diff --git a/.gitignore b/.gitignore index 9952c2a..3c64d29 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /schema-generate +.idea +test/*_gen diff --git a/.travis.yml b/.travis.yml index 4cb90d6..0f5c071 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: go -go_import_path: github.com/a-h/generate - script: - make codecheck - make test diff --git a/Makefile b/Makefile index bccc819..b2141fb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PKG := github.com/a-h/generate +PKG := . CMD := $(PKG)/cmd/schema-generate BIN := schema-generate @@ -8,7 +8,7 @@ BIN := schema-generate all: clean $(BIN) -$(BIN): +$(BIN): generator.go jsonschema.go cmd/schema-generate/main.go @echo "+ Building $@" CGO_ENABLED="0" go build -v -o $@ $(CMD) @@ -16,14 +16,25 @@ clean: @echo "+ Cleaning $(PKG)" go clean -i $(PKG)/... rm -f $(BIN) + rm -rf test/*_gen # Test +# generate sources +JSON := $(wildcard test/*.json) +GENERATED_SOURCE := $(patsubst %.json,%_gen/generated.go,$(JSON)) +test/%_gen/generated.go: test/%.json + @echo "\n+ Generating code for $@" + @D=$(shell echo $^ | sed 's/.json/_gen/'); \ + [ ! -d $$D ] && mkdir -p $$D || true + ./schema-generate -o $@ -p $(shell echo $^ | sed 's/test\///; s/.json//') $^ + .PHONY: test codecheck fmt lint vet -test: - @echo "+ Executing tests for $(PKG)" +test: $(BIN) $(GENERATED_SOURCE) + @echo "\n+ Executing tests for $(PKG)" go test -v -race -cover $(PKG)/... + codecheck: fmt lint vet diff --git a/README.md b/README.md index 0cd2c82..466e521 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # generate -Generates Go (golang) Structs from JSON schema. +Generates Go (golang) Structs and Validation code from JSON schema. # Requirements diff --git a/cmd/schema-generate/main.go b/cmd/schema-generate/main.go index af4029d..44d8aac 100644 --- a/cmd/schema-generate/main.go +++ b/cmd/schema-generate/main.go @@ -3,17 +3,11 @@ package main import ( - "encoding/json" "flag" "fmt" + "github.com/a-h/generate" "io" - "io/ioutil" "os" - "sort" - "strings" - - "github.com/a-h/generate" - "github.com/a-h/generate/jsonschema" ) var ( @@ -42,42 +36,18 @@ func main() { os.Exit(1) } - schemas := make([]*jsonschema.Schema, len(inputFiles)) - for i, file := range inputFiles { - b, err := ioutil.ReadFile(file) - if err != nil { - fmt.Fprintln(os.Stderr, "Failed to read the input file with error ", err) - return - } - - schemas[i], err = jsonschema.Parse(string(b)) - if err != nil { - if jsonError, ok := err.(*json.SyntaxError); ok { - line, character, lcErr := lineAndCharacter(b, int(jsonError.Offset)) - fmt.Fprintf(os.Stderr, "Cannot parse JSON schema due to a syntax error at %s line %d, character %d: %v\n", file, line, character, jsonError.Error()) - if lcErr != nil { - fmt.Fprintf(os.Stderr, "Couldn't find the line and character position of the error due to error %v\n", lcErr) - } - return - } - if jsonError, ok := err.(*json.UnmarshalTypeError); ok { - line, character, lcErr := lineAndCharacter(b, int(jsonError.Offset)) - fmt.Fprintf(os.Stderr, "The JSON type '%v' cannot be converted into the Go '%v' type on struct '%s', field '%v'. See input file %s line %d, character %d\n", jsonError.Value, jsonError.Type.Name(), jsonError.Struct, jsonError.Field, file, line, character) - if lcErr != nil { - fmt.Fprintf(os.Stderr, "Couldn't find the line and character position of the error due to error %v\n", lcErr) - } - return - } - fmt.Fprintf(os.Stderr, "Failed to parse the input JSON schema file %s with error %v\n", file, err) - return - } + schemas, err := generate.ReadInputFiles(inputFiles) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) } g := generate.New(schemas...) - structs, aliases, err := g.CreateTypes() + err = g.CreateTypes() if err != nil { fmt.Fprintln(os.Stderr, "Failure generating structs: ", err) + os.Exit(1) } var w io.Writer = os.Stdout @@ -91,97 +61,5 @@ func main() { } } - output(w, structs, aliases) -} - -func lineAndCharacter(bytes []byte, offset int) (line int, character int, err error) { - lf := byte(0x0A) - - if offset > len(bytes) { - return 0, 0, fmt.Errorf("couldn't find offset %d in %d bytes", offset, len(bytes)) - } - - // Humans tend to count from 1. - line = 1 - - for i, b := range bytes { - if b == lf { - line++ - character = 0 - } - character++ - if i == offset { - return line, character, nil - } - } - - return 0, 0, fmt.Errorf("couldn't find offset %d in %d bytes", offset, len(bytes)) -} - -func getOrderedFieldNames(m map[string]generate.Field) []string { - keys := make([]string, len(m)) - idx := 0 - for k := range m { - keys[idx] = k - idx++ - } - sort.Strings(keys) - return keys -} - -func getOrderedStructNames(m map[string]generate.Struct) []string { - keys := make([]string, len(m)) - idx := 0 - for k := range m { - keys[idx] = k - idx++ - } - sort.Strings(keys) - return keys -} - -func output(w io.Writer, structs map[string]generate.Struct, aliases map[string]generate.Field) { - fmt.Fprintln(w, "// Code generated by schema-generate. DO NOT EDIT.") - fmt.Fprintln(w) - fmt.Fprintf(w, "package %v\n", *p) - - for _, k := range getOrderedFieldNames(aliases) { - a := aliases[k] - - fmt.Fprintln(w, "") - fmt.Fprintf(w, "// %s\n", a.Name) - fmt.Fprintf(w, "type %s %s\n", a.Name, a.Type) - } - - for _, k := range getOrderedStructNames(structs) { - s := structs[k] - - fmt.Fprintln(w, "") - outputNameAndDescriptionComment(s.Name, s.Description, w) - fmt.Fprintf(w, "type %s struct {\n", s.Name) - - for _, fieldKey := range getOrderedFieldNames(s.Fields) { - f := s.Fields[fieldKey] - - // Only apply omitempty if the field is not required. - omitempty := ",omitempty" - if f.Required { - omitempty = "" - } - - fmt.Fprintf(w, " %s %s `json:\"%s%s\"`\n", f.Name, f.Type, f.JSONName, omitempty) - } - - fmt.Fprintln(w, "}") - } -} - -func outputNameAndDescriptionComment(name, description string, w io.Writer) { - if strings.Index(description, "\n") == -1 { - fmt.Fprintf(w, "// %s %s\n", name, description) - return - } - - dl := strings.Split(description, "\n") - fmt.Fprintf(w, "// %s %s\n", name, strings.Join(dl, "\n// ")) + generate.Output(w, g, *p) } diff --git a/cmd/schema-generate/main_test.go b/cmd/schema-generate/main_test.go index b1d33c3..06ab7d0 100644 --- a/cmd/schema-generate/main_test.go +++ b/cmd/schema-generate/main_test.go @@ -1,117 +1 @@ package main - -import ( - "bytes" - "io" - "io/ioutil" - "reflect" - "strings" - "testing" - - "github.com/a-h/generate" -) - -func TestThatFieldNamesAreOrdered(t *testing.T) { - m := map[string]generate.Field{ - "z": {}, - "b": {}, - } - - actual := getOrderedFieldNames(m) - expected := []string{"b", "z"} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("expected %s and actual %s should match in order", strings.Join(expected, ", "), strings.Join(actual, ",")) - } -} - -func TestThatStructNamesAreOrdered(t *testing.T) { - m := map[string]generate.Struct{ - "c": {}, - "b": {}, - "a": {}, - } - - actual := getOrderedStructNames(m) - expected := []string{"a", "b", "c"} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("expected %s and actual %s should match in order", strings.Join(expected, ", "), strings.Join(actual, ",")) - } -} - -func TestThatThePackageCanBeSet(t *testing.T) { - pkg := "testpackage" - p = &pkg - - r, w := io.Pipe() - - go output(w, make(map[string]generate.Struct), make(map[string]generate.Field)) - - lr := io.LimitedReader{R: r, N: 72} - bs, _ := ioutil.ReadAll(&lr) - output := bytes.NewBuffer(bs).String() - - want := `// Code generated by schema-generate. DO NOT EDIT. - -package testpackage -` - if output != want { - t.Error("Unexpected package declaration: ", output) - } -} - -func TestLineAndCharacterFromOffset(t *testing.T) { - tests := []struct { - In []byte - Offset int - ExpectedLine int - ExpectedCharacter int - ExpectedError bool - }{ - { - In: []byte("Line 1\nLine 2"), - Offset: 6, - ExpectedLine: 2, - ExpectedCharacter: 1, - }, - { - In: []byte("Line 1\r\nLine 2"), - Offset: 7, - ExpectedLine: 2, - ExpectedCharacter: 1, - }, - { - In: []byte("Line 1\nLine 2"), - Offset: 0, - ExpectedLine: 1, - ExpectedCharacter: 1, - }, - { - In: []byte("Line 1\nLine 2"), - Offset: 200, - ExpectedLine: 0, - ExpectedCharacter: 0, - ExpectedError: true, - }, - { - In: []byte("Line 1\nLine 2"), - Offset: -1, - ExpectedLine: 0, - ExpectedCharacter: 0, - ExpectedError: true, - }, - } - - for _, test := range tests { - actualLine, actualCharacter, err := lineAndCharacter(test.In, test.Offset) - if err != nil && !test.ExpectedError { - t.Errorf("Unexpected error for input %s at offset %d: %v", test.In, test.Offset, err) - continue - } - - if actualLine != test.ExpectedLine || actualCharacter != test.ExpectedCharacter { - t.Errorf("For '%s' at offset %d, expected %d:%d, but got %d:%d", test.In, test.Offset, test.ExpectedLine, test.ExpectedCharacter, actualLine, actualCharacter) - } - } -} diff --git a/generator.go b/generator.go index 2b94b6f..bb25d4f 100644 --- a/generator.go +++ b/generator.go @@ -1,308 +1,277 @@ -// Package generate creates Go structs from JSON schemas. package generate import ( "bytes" "errors" "fmt" - "net/url" - "path" - "sort" "strings" "unicode" - - "github.com/a-h/generate/jsonschema" ) // Generator will produce structs from the JSON schema. type Generator struct { - schemas []*jsonschema.Schema + schemas []*Schema + resolver *RefResolver + Structs map[string]Struct + Aliases map[string]Field + // cache for reference types; k=url v=type + refs map[string]string + anonCount int } // New creates an instance of a generator which will produce structs. -func New(schemas ...*jsonschema.Schema) *Generator { +func New(schemas ...*Schema) *Generator { return &Generator{ - schemas: schemas, + schemas: schemas, + resolver: NewRefResolver(schemas), + Structs: make(map[string]Struct), + Aliases: make(map[string]Field), + refs: make(map[string]string), } } // CreateTypes creates types from the JSON schemas, keyed by the golang name. -func (g *Generator) CreateTypes() (structs map[string]Struct, aliases map[string]Field, err error) { - schemaIDs := make([]*url.URL, len(g.schemas)) - for i, schema := range g.schemas { - if schema.ID() != "" { - schemaIDs[i], err = url.Parse(schema.ID()) - if err != nil { - return - } - } - } - - // Extract nested and complex types from the JSON schemas. - types := map[string]*jsonschema.Schema{} - for i, schema := range g.schemas { - for name, typ := range schema.ExtractTypes() { - if schemaIDs[i] != nil { - name = schemaIDs[i].ResolveReference(&url.URL{Fragment: name[1:]}).String() - } - if typ.Reference == "" { - types[name] = typ - } - } +func (g *Generator) CreateTypes() (err error) { + if err := g.resolver.Init(); err != nil { + return err } - structs = make(map[string]Struct) - aliases = make(map[string]Field) - errs := []error{} - - for _, typeKey := range getOrderedKeyNamesFromSchemaMap(types) { - v := types[typeKey] - - if (v.TypeValue == "object" || v.TypeValue == nil) && len(v.Properties) > 0 { - s, errtype := createStruct(typeKey, v, types) - if errtype != nil { - errs = append(errs, errtype...) - } - - if _, ok := structs[s.Name]; ok { - errs = append(errs, errors.New("Duplicate struct name : "+s.Name)) - } - - structs[s.Name] = s + // extract the types + for _, schema := range g.schemas { + name := g.getSchemaName("", schema) + if rootType, err := g.processSchema(name, schema); err != nil { + return err } else { - a, errtype := createAlias(typeKey, v, types) - if errtype != nil { - errs = append(errs, errtype...) + // ugh: if it was anything but a struct the type will not be the name... + if rootType != "*"+name { + a := Field{ + Name: name, + JSONName: "", + Type: rootType, + Required: false, + Description: schema.Description, + } + g.Aliases[a.Name] = a } - - aliases[a.Name] = a } } - - if len(errs) > 0 { - err = errors.New(joinErrors(errs)) - } return } -// createStruct creates a struct type from the JSON schema. -func createStruct(typeKey string, schema *jsonschema.Schema, types map[string]*jsonschema.Schema) (s Struct, errs []error) { - typeKeyURI, err := url.Parse(typeKey) - if err != nil { - errs = append(errs, err) - } - - fields, err := getFields(typeKeyURI, schema.Properties, types, schema.Required) - if err != nil { - errs = append(errs, err) - } - - structName := getTypeName(typeKeyURI, schema, 1) - if err != nil { - errs = append(errs, err) - } - - s = Struct{ - ID: typeKey, - Name: structName, - Description: schema.Description, - Fields: fields, - } - - return -} - -// createAlias creates a simple alias type from the JSON schema. -func createAlias(typeKey string, schema *jsonschema.Schema, types map[string]*jsonschema.Schema) (a Field, errs []error) { - typeKeyURI, err := url.Parse(typeKey) - if err != nil { - errs = append(errs, err) - } - - aliasName := getTypeName(typeKeyURI, schema, 1) - if err != nil { - errs = append(errs, err) - } - - tn, err := getTypeForField(typeKeyURI, typeKey, aliasName, schema, types, true) - if err != nil { - errs = append(errs, err) - } - - a = Field{ - Name: aliasName, - JSONName: "", - Type: tn, - Required: false, +// process a block of definitions +func (g *Generator) processDefinitions(schema *Schema) error { + for key, subSchema := range schema.Definitions { + if _, err := g.processSchema(getGolangName(key), subSchema); err != nil { + return err + } } - - return + return nil } -func joinErrors(errs []error) string { - var buffer bytes.Buffer - - for idx, err := range errs { - buffer.WriteString(err.Error()) - - if idx+1 < len(errs) { - buffer.WriteString(", ") +// process a reference string +func (g *Generator) processReference(schema *Schema) (string, error) { + schemaPath := g.resolver.GetPath(schema) + if schema.Reference == "" { + return "", errors.New("processReference empty reference: " + schemaPath) + } + if refSchema, err := g.resolver.GetSchemaByReference(schema); err != nil { + return "", errors.New("processReference: reference \"" + schema.Reference + "\" not found at \"" + schemaPath + "\"") + } else { + if refSchema.GeneratedType == "" { + // reference is not resolved yet. Do that now. + refSchemaName := g.getSchemaName("", refSchema) + if typeName, err := g.processSchema(refSchemaName, refSchema); err != nil { + return "", err + } else { + return typeName, nil + } + } else { + return refSchema.GeneratedType, nil } } - - return buffer.String() } -func getOrderedKeyNamesFromSchemaMap(m map[string]*jsonschema.Schema) []string { - keys := make([]string, len(m)) - idx := 0 - for k := range m { - keys[idx] = k - idx++ +// returns the type refered to by schema after resolving all dependencies +func (g *Generator) processSchema(schemaName string, schema *Schema) (typ string, err error) { + if len(schema.Definitions) > 0 { + g.processDefinitions(schema) + } + schema.FixMissingTypeValue() + // if we have multiple schema types, the golang type will be interface{} + typ = "interface{}" + types, isMultiType := schema.MultiType() + if len(types) > 0 { + for _, schemaType := range types { + name := schemaName + if isMultiType { + name = name + "_" + schemaType + } + switch schemaType { + case "object": + if rv, err := g.processObject(name, schema); err != nil { + return "", err + } else { + if !isMultiType { + return rv, nil + } + } + case "array": + if rv, err := g.processArray(name, schema); err != nil { + return "", err + } else { + if !isMultiType { + return rv, nil + } + } + default: + if rv, err := getPrimitiveTypeName(schemaType, "", false); err != nil { + return "", err + } else { + if !isMultiType { + return rv, nil + } + } + } + } + } else { + if schema.Reference != "" { + return g.processReference(schema) + } } - sort.Strings(keys) - return keys + return // return interface{} } -func getFields(parentTypeKey *url.URL, properties map[string]*jsonschema.Schema, - types map[string]*jsonschema.Schema, requiredFields []string) (field map[string]Field, err error) { - fields := map[string]Field{} - - missingTypes := []string{} - errors := []error{} - - for _, fieldName := range getOrderedKeyNamesFromSchemaMap(properties) { - v := properties[fieldName] - - golangName := getGolangName(fieldName) - tn, err := getTypeForField(parentTypeKey, fieldName, golangName, v, types, true) - +// name: name of this array, usually the js key +// schema: items element +func (g *Generator) processArray(name string, schema *Schema) (typeStr string, err error) { + if schema.Items != nil { + // subType: fallback name in case this array contains inline object without a title + subName := g.getSchemaName(name+"Items", schema.Items) + subTyp, err := g.processSchema(subName, schema.Items) if err != nil { - missingTypes = append(missingTypes, golangName) - errors = append(errors, err) + return "", err } - - f := Field{ - Name: golangName, - JSONName: fieldName, - // Look up the types, try references first, then drop to the built-in types. - Type: tn, - Required: contains(requiredFields, fieldName), + if finalType, err := getPrimitiveTypeName("array", subTyp, true); err != nil { + return "", err + } else { + // only alias root arrays + if schema.Parent == nil { + array := Field{ + Name: name, + JSONName: "", + Type: finalType, + Required: contains(schema.Required, name), + Description: schema.Description, + } + g.Aliases[array.Name] = array + } + return finalType, nil } - - fields[f.Name] = f } - - if len(missingTypes) > 0 { - return fields, fmt.Errorf("missing types for %s with errors %s", - strings.Join(missingTypes, ","), joinErrors(errors)) - } - - return fields, nil + return "[]interface{}", nil } -func contains(s []string, e string) bool { - for _, a := range s { - if a == e { - return true +// name: name of the struct (calculated by caller) +// schema: detail incl properties & child objects +// returns: generated type +func (g *Generator) processObject(name string, schema *Schema) (typ string, err error) { + strct := Struct{ + ID: schema.ID(), + Name: name, + Description: schema.Description, + Fields: make(map[string]Field, len(schema.Properties)), + } + // cache the object name in case any sub-schemas recursively reference it + schema.GeneratedType = "*" + name + // regular properties + for propKey, prop := range schema.Properties { + fieldName := getGolangName(propKey) + // calculate sub-schema name here, may not actually be used depending on type of schema! + subSchemaName := g.getSchemaName(fieldName, prop) + if fieldType, err := g.processSchema(subSchemaName, prop); err != nil { + return "", err + } else { + f := Field{ + Name: fieldName, + JSONName: propKey, + Type: fieldType, + Required: contains(schema.Required, propKey), + Description: prop.Description, + } + if f.Required { + strct.GenerateCode = true + } + strct.Fields[f.Name] = f } } - return false -} - -func getTypeForField(parentTypeKey *url.URL, fieldName string, fieldGoName string, - fieldSchema *jsonschema.Schema, types map[string]*jsonschema.Schema, pointer bool) (typeName string, err error) { - // If there's no schema, or the field can be more than one type, we have to use interface{} and allow the caller to use type assertions to determine - // the actual underlying type. - if fieldSchema == nil { - return "interface{}", nil - } - - majorType, multiple := fieldSchema.Type() - if multiple { - return "interface{}", nil - } - - var subType string - - // Look up by named reference. - if fieldSchema.Reference != "" { - // Resolve reference URI relative to schema's ID (URI). - ref, err := url.Parse(fieldSchema.Reference) + // additionalProperties with typed sub-schema + if schema.AdditionalProperties != nil && schema.AdditionalProperties.AdditionalPropertiesBool == nil { + ap := (*Schema)(schema.AdditionalProperties) + apName := g.getSchemaName("", ap) + subTyp, err := g.processSchema(apName, ap) if err != nil { return "", err } - ref = parentTypeKey.ResolveReference(ref) - - if t, ok := types[ref.String()]; ok { - sn := getTypeName(ref, t, 1) - - majorType = "object" - subType = sn + mapTyp := "map[string]" + subTyp + // If this object is inline property for another object, and only contains additional properties, we can + // collapse the structure down to a map. + // + // If this object is a definition and only contains additional properties, we can't do that or we end up with + // no struct + isDefinitionObject := strings.HasPrefix(schema.PathElement, "definitions") + if len(schema.Properties) == 0 && !isDefinitionObject { + // since there are no regular properties, we don't need to emit a struct for this object - return the + // additionalProperties map type. + return mapTyp, nil } else { - return "", fmt.Errorf("failed to resolve the reference %s", ref) + // this struct will have both regular and additional properties + f := Field{ + Name: "AdditionalProperties", + JSONName: "-", + Type: mapTyp, + Required: false, + Description: "", + } + strct.Fields[f.Name] = f + // setting this will cause marshal code to be emitted in Output() + strct.GenerateCode = true + strct.AdditionalType = subTyp } } - - // Look up any embedded types. - if subType == "" && (majorType == "object" || majorType == "") { - if len(fieldSchema.Properties) == 0 && len(fieldSchema.AdditionalProperties) > 0 { - if len(fieldSchema.AdditionalProperties) == 1 { - sn, _ := getTypeForField(parentTypeKey, fieldName, fieldGoName, - fieldSchema.AdditionalProperties[0], types, pointer) - subType = "map[string]" + sn - pointer = false - } else { - subType = "map[string]interface{}" - pointer = false + // additionalProperties as either true (everything) or false (nothing) + if schema.AdditionalProperties != nil && schema.AdditionalProperties.AdditionalPropertiesBool != nil { + if *schema.AdditionalProperties.AdditionalPropertiesBool == true { + // everything is valid additional + subTyp := "map[string]interface{}" + f := Field{ + Name: "AdditionalProperties", + JSONName: "-", + Type: subTyp, + Required: false, + Description: "", } + strct.Fields[f.Name] = f + // setting this will cause marshal code to be emitted in Output() + strct.GenerateCode = true + strct.AdditionalType = "interface{}" } else { - ref := joinURLFragmentPath(parentTypeKey, "properties/"+fieldName) - - // Root schema without properties, try array item instead - if _, ok := types[ref.String()]; !ok && isRootSchemaKey(parentTypeKey) { - ref = joinURLFragmentPath(parentTypeKey, "arrayitems") - } - - if parentType, ok := types[ref.String()]; ok { - sn := getTypeName(ref, parentType, 1) - subType = sn - } else { - subType = "undefined" - } + // nothing + strct.GenerateCode = true + strct.AdditionalType = "false" } } - - // Find named array references. - if majorType == "array" { - s, _ := getTypeForField(parentTypeKey, fieldName, fieldGoName, fieldSchema.Items, types, true) - subType = s - } - - name, err := getPrimitiveTypeName(majorType, subType, pointer) - - if err != nil { - return name, fmt.Errorf("failed to get the type for %s with error %s", - fieldGoName, - err.Error()) - } - - return name, nil -} - -// isRootSchemaKey returns whether a given type key references the root schema. -func isRootSchemaKey(typeKey *url.URL) bool { - return typeKey.Fragment == "" + g.Structs[strct.Name] = strct + // objects are always a pointer + return getPrimitiveTypeName("object", name, true) } -// joinURLFragmentPath joins elem onto u.Fragment, adding a separating slash. -func joinURLFragmentPath(base *url.URL, elem string) *url.URL { - url := *base - if url.Fragment == "" { - url.Fragment = "/" +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } } - url.Fragment = path.Join(url.Fragment, elem) - return &url + return false } func getPrimitiveTypeName(schemaType string, subType string, pointer bool) (name string, err error) { @@ -321,6 +290,9 @@ func getPrimitiveTypeName(schemaType string, subType string, pointer bool) (name case "null": return "nil", nil case "object": + if subType == "" { + return "error_creating_object", errors.New("can't create an object of an empty subtype") + } if pointer { return "*" + subType, nil } @@ -333,47 +305,30 @@ func getPrimitiveTypeName(schemaType string, subType string, pointer bool) (name schemaType, subType) } -// getTypeName makes a golang type name from an input reference in the form of #/definitions/address -// The parts refers to the number of segments from the end to take as the name. -func getTypeName(reference *url.URL, structType *jsonschema.Schema, n int) string { - if len(structType.Title) > 0 { - return getGolangName(structType.Title) - } - - if isRootSchemaKey(reference) { - rootName := structType.Title - - if rootName == "" { - rootName = structType.Description - } - - if rootName == "" { - rootName = "Root" - } - - return getGolangName(rootName) +// return a name for this (sub-)schema. +func (g *Generator) getSchemaName(keyName string, schema *Schema) string { + if len(schema.Title) > 0 { + return getGolangName(schema.Title) } - parts := strings.Split(reference.Fragment, "/") - partsToUse := parts[len(parts)-n:] - - sb := bytes.Buffer{} - - for _, p := range partsToUse { - sb.WriteString(getGolangName(p)) + if keyName != "" { + return getGolangName(keyName) } - result := sb.String() - - if result == "" { + if schema.Parent == nil { return "Root" } - if structType.NameCount > 1 { - result = fmt.Sprintf("%v%v", result, structType.NameCount) + if schema.JSONKey != "" { + return getGolangName(schema.JSONKey) + } + if schema.Parent != nil && schema.Parent.JSONKey != "" { + // ugh... + return getGolangName(schema.Parent.JSONKey + "Item") } - return result + g.anonCount++ + return fmt.Sprintf("Anonymous%d", g.anonCount) } // getGolangName strips invalid characters out of golang struct or field names. @@ -436,6 +391,9 @@ type Struct struct { // Description of the struct Description string Fields map[string]Field + + GenerateCode bool + AdditionalType string } // Field defines the data required to generate a field in Go. @@ -448,5 +406,6 @@ type Field struct { // from the JSON schema. Type string // Required is set to true when the field is required. - Required bool + Required bool + Description string } diff --git a/generator_test.go b/generator_test.go index 69c7e03..7e02e21 100644 --- a/generator_test.go +++ b/generator_test.go @@ -2,12 +2,9 @@ package generate import ( "encoding/json" - "net/url" "reflect" "strings" "testing" - - "github.com/a-h/generate/jsonschema" ) func TestThatCapitalisationOccursCorrectly(t *testing.T) { @@ -45,152 +42,167 @@ func TestThatCapitalisationOccursCorrectly(t *testing.T) { } } -func TestThatStructsAreNamedWell(t *testing.T) { - tests := []struct { - input string - schema jsonschema.Schema - expected string - }{ - { - input: "/definitions/address", - expected: "Address", - }, - { - input: "/Example", - expected: "Example", - }, - { - input: "/Example", - expected: "Example", - schema: jsonschema.Schema{ - NameCount: 1, - }, - }, - { - input: "/Example", - expected: "Example2", - schema: jsonschema.Schema{ - NameCount: 2, - }, - }, - { - input: "", - expected: "TheRootName", - schema: jsonschema.Schema{ - Title: "TheRootName", - }, - }, - } - - for idx, test := range tests { - actual := getTypeName(&url.URL{Fragment: test.input}, &test.schema, 1) - if actual != test.expected { - t.Errorf("Test %d failed: For input \"%s\", expected \"%s\", got \"%s\"", idx, test.input, test.expected, actual) - } - } -} - func TestFieldGeneration(t *testing.T) { - properties := map[string]*jsonschema.Schema{ + properties := map[string]*Schema{ "property1": {TypeValue: "string"}, "property2": {Reference: "#/definitions/address"}, - "property3": {TypeValue: "object", AdditionalProperties: []*jsonschema.Schema{{TypeValue: "integer"}}}, - "property4": {TypeValue: "object", AdditionalProperties: []*jsonschema.Schema{{TypeValue: "integer"}, {TypeValue: "integer"}}}, - "property5": {TypeValue: "object", AdditionalProperties: []*jsonschema.Schema{{TypeValue: "object", Properties: map[string]*jsonschema.Schema{"subproperty1": {TypeValue: "integer"}}}}}, - "property6": {TypeValue: "object", AdditionalProperties: []*jsonschema.Schema{{TypeValue: "object", Properties: map[string]*jsonschema.Schema{"subproperty1": {TypeValue: "integer"}}}}}, + // test sub-objects with properties or additionalProperties + "property3": {TypeValue: "object", Title: "SubObj1", Properties: map[string]*Schema{"name": {TypeValue: "string"}}}, + "property4": {TypeValue: "object", Title: "SubObj2", AdditionalProperties: &AdditionalProperties{TypeValue: "integer"}}, + // test sub-objects with properties composed of objects + "property5": {TypeValue: "object", Title: "SubObj3", Properties: map[string]*Schema{"SubObj3a": {TypeValue: "object", Properties: map[string]*Schema{"subproperty1": {TypeValue: "integer"}}}}}, + // test sub-objects with additionalProperties composed of objects + "property6": {TypeValue: "object", Title: "SubObj4", AdditionalProperties: &AdditionalProperties{TypeValue: "object", Title: "SubObj4a", Properties: map[string]*Schema{"subproperty1": {TypeValue: "integer"}}}}, + // test sub-objects without titles + "property7": {TypeValue: "object"}, + // test sub-objects with properties AND additionalProperties + "property8": {TypeValue: "object", Title: "SubObj5", Properties: map[string]*Schema{"name": {TypeValue: "string"}}, AdditionalProperties: &AdditionalProperties{TypeValue: "integer"}}, } - lookupTypes := map[string]*jsonschema.Schema{ - "#/definitions/address": {}, - "#/properties/property5": properties["property5"].AdditionalProperties[0], + requiredFields := []string{"property2"} + + root := Schema{ + SchemaType: "http://localhost", + Title: "TestFieldGeneration", + TypeValue: "object", + Properties: properties, + Definitions: map[string]*Schema{ + "address": {TypeValue: "object"}, + }, + Required: requiredFields, } + root.Init() + g := New(&root) + err := g.CreateTypes() - requiredFields := []string{"property2"} - result, err := getFields(&url.URL{}, properties, lookupTypes, requiredFields) + // Output(os.Stderr, g, "test") if err != nil { t.Error("Failed to get the fields: ", err) } - if len(result) != 6 { - t.Errorf("Expected 6 results, but got %d results", len(result)) + if len(g.Structs) != 8 { + t.Errorf("Expected 8 results, but got %d results", len(g.Structs)) } - testField(result["Property1"], "property1", "Property1", "string", false, t) - testField(result["Property2"], "property2", "Property2", "*Address", true, t) - testField(result["Property3"], "property3", "Property3", "map[string]int", false, t) - testField(result["Property4"], "property4", "Property4", "map[string]interface{}", false, t) - testField(result["Property5"], "property5", "Property5", "map[string]*Property5", false, t) - testField(result["Property6"], "property6", "Property6", "map[string]*undefined", false, t) + testField(g.Structs["TestFieldGeneration"].Fields["Property1"], "property1", "Property1", "string", false, t) + testField(g.Structs["TestFieldGeneration"].Fields["Property2"], "property2", "Property2", "*Address", true, t) + testField(g.Structs["TestFieldGeneration"].Fields["Property3"], "property3", "Property3", "*SubObj1", false, t) + testField(g.Structs["TestFieldGeneration"].Fields["Property4"], "property4", "Property4", "map[string]int", false, t) + testField(g.Structs["TestFieldGeneration"].Fields["Property5"], "property5", "Property5", "*SubObj3", false, t) + testField(g.Structs["TestFieldGeneration"].Fields["Property6"], "property6", "Property6", "map[string]*SubObj4a", false, t) + testField(g.Structs["TestFieldGeneration"].Fields["Property7"], "property7", "Property7", "*Property7", false, t) + testField(g.Structs["TestFieldGeneration"].Fields["Property8"], "property8", "Property8", "*SubObj5", false, t) + + testField(g.Structs["SubObj1"].Fields["Name"], "name", "Name", "string", false, t) + testField(g.Structs["SubObj3"].Fields["SubObj3a"], "SubObj3a", "SubObj3a", "*SubObj3a", false, t) + testField(g.Structs["SubObj4a"].Fields["Subproperty1"], "subproperty1", "Subproperty1", "int", false, t) + + testField(g.Structs["SubObj5"].Fields["Name"], "name", "Name", "string", false, t) + testField(g.Structs["SubObj5"].Fields["AdditionalProperties"], "-", "AdditionalProperties", "map[string]int", false, t) + + if strct, ok := g.Structs["Property7"]; !ok { + t.Fatal("Property7 wasn't generated") + } else { + if len(strct.Fields) != 0 { + t.Fatal("Property7 expected 0 fields") + } + } } func TestFieldGenerationWithArrayReferences(t *testing.T) { - properties := map[string]*jsonschema.Schema{ + properties := map[string]*Schema{ "property1": {TypeValue: "string"}, "property2": { TypeValue: "array", - Items: &jsonschema.Schema{ + Items: &Schema{ Reference: "#/definitions/address", }, }, "property3": { TypeValue: "array", - Items: &jsonschema.Schema{ + Items: &Schema{ TypeValue: "object", - AdditionalProperties: []*jsonschema.Schema{{TypeValue: "integer"}}, + AdditionalProperties: (*AdditionalProperties)(&Schema{TypeValue: "integer"}), + }, + }, + "property4": { + TypeValue: "array", + Items: &Schema{ + Reference: "#/definitions/outer", }, }, } - lookupTypes := map[string]*jsonschema.Schema{ - "#/definitions/address": {}, + requiredFields := []string{"property2"} + + root := Schema{ + SchemaType: "http://localhost", + Title: "TestFieldGenerationWithArrayReferences", + TypeValue: "object", + Properties: properties, + Definitions: map[string]*Schema{ + "address": {TypeValue: "object"}, + "outer": {TypeValue: "array", Items: &Schema{Reference: "#/definitions/inner"}}, + "inner": {TypeValue: "object"}, + }, + Required: requiredFields, } + root.Init() - requiredFields := []string{"property2"} - result, err := getFields(&url.URL{}, properties, lookupTypes, requiredFields) + g := New(&root) + err := g.CreateTypes() + + //Output(os.Stderr, g, "test") if err != nil { t.Error("Failed to get the fields: ", err) } - if len(result) != 3 { - t.Errorf("Expected 3 results, but got %d results", len(result)) + if len(g.Structs) != 3 { + t.Errorf("Expected 3 results, but got %d results", len(g.Structs)) } - testField(result["Property1"], "property1", "Property1", "string", false, t) - testField(result["Property2"], "property2", "Property2", "[]*Address", true, t) - testField(result["Property3"], "property3", "Property3", "[]map[string]int", false, t) + testField(g.Structs["TestFieldGenerationWithArrayReferences"].Fields["Property1"], "property1", "Property1", "string", false, t) + testField(g.Structs["TestFieldGenerationWithArrayReferences"].Fields["Property2"], "property2", "Property2", "[]*Address", true, t) + testField(g.Structs["TestFieldGenerationWithArrayReferences"].Fields["Property3"], "property3", "Property3", "[]map[string]int", false, t) + testField(g.Structs["TestFieldGenerationWithArrayReferences"].Fields["Property4"], "property4", "Property4", "[][]*Inner", false, t) } func testField(actual Field, expectedJSONName string, expectedName string, expectedType string, expectedToBeRequired bool, t *testing.T) { if actual.JSONName != expectedJSONName { - t.Errorf("JSONName - expected %s, got %s", expectedJSONName, actual.JSONName) + t.Errorf("JSONName - expected \"%s\", got \"%s\"", expectedJSONName, actual.JSONName) } if actual.Name != expectedName { - t.Errorf("Name - expected %s, got %s", expectedName, actual.Name) + t.Errorf("Name - expected \"%s\", got \"%s\"", expectedName, actual.Name) } if actual.Type != expectedType { - t.Errorf("Type - expected %s, got %s", expectedType, actual.Type) + t.Errorf("Type - expected \"%s\", got \"%s\"", expectedType, actual.Type) } if actual.Required != expectedToBeRequired { - t.Errorf("Required - expected %v, got %v", expectedToBeRequired, actual.Required) + t.Errorf("Required - expected \"%v\", got \"%v\"", expectedToBeRequired, actual.Required) } } func TestNestedStructGeneration(t *testing.T) { - root := &jsonschema.Schema{} + root := &Schema{} root.Title = "Example" - root.Properties = map[string]*jsonschema.Schema{ + root.Properties = map[string]*Schema{ "property1": { TypeValue: "object", - Properties: map[string]*jsonschema.Schema{ + Properties: map[string]*Schema{ "subproperty1": {TypeValue: "string"}, }, }, } + root.Init() + g := New(root) - results, _, err := g.CreateTypes() + err := g.CreateTypes() + results := g.Structs + + //Output(os.Stderr, g, "test") if err != nil { t.Error("Failed to create structs: ", err) @@ -214,19 +226,24 @@ func TestNestedStructGeneration(t *testing.T) { } func TestEmptyNestedStructGeneration(t *testing.T) { - root := &jsonschema.Schema{} + root := &Schema{} root.Title = "Example" - root.Properties = map[string]*jsonschema.Schema{ + root.Properties = map[string]*Schema{ "property1": { TypeValue: "object", - Properties: map[string]*jsonschema.Schema{ + Properties: map[string]*Schema{ "nestedproperty1": {TypeValue: "string"}, }, }, } + root.Init() + g := New(root) - results, _, err := g.CreateTypes() + err := g.CreateTypes() + results := g.Structs + + // Output(os.Stderr, g, "test") if err != nil { t.Error("Failed to create structs: ", err) @@ -279,22 +296,27 @@ func getStructNamesFromMap(m map[string]Struct) []string { } func TestStructGeneration(t *testing.T) { - root := &jsonschema.Schema{} + root := &Schema{} root.Title = "RootElement" - root.Definitions = make(map[string]*jsonschema.Schema) - root.Definitions["address"] = &jsonschema.Schema{ - Properties: map[string]*jsonschema.Schema{ + root.Definitions = make(map[string]*Schema) + root.Definitions["address"] = &Schema{ + Properties: map[string]*Schema{ "address1": {TypeValue: "string"}, "zip": {TypeValue: "number"}, }, } - root.Properties = map[string]*jsonschema.Schema{ + root.Properties = map[string]*Schema{ "property1": {TypeValue: "string"}, "property2": {Reference: "#/definitions/address"}, } + root.Init() + g := New(root) - results, _, err := g.CreateTypes() + err := g.CreateTypes() + results := g.Structs + + // Output(os.Stderr, g, "test") if err != nil { t.Error("Failed to create structs: ", err) @@ -306,21 +328,26 @@ func TestStructGeneration(t *testing.T) { } func TestArrayGeneration(t *testing.T) { - root := &jsonschema.Schema{ + root := &Schema{ Title: "Array of Artists Example", TypeValue: "array", - Items: &jsonschema.Schema{ + Items: &Schema{ Title: "Artist", TypeValue: "object", - Properties: map[string]*jsonschema.Schema{ + Properties: map[string]*Schema{ "name": {TypeValue: "string"}, "birthyear": {TypeValue: "number"}, }, }, } + root.Init() + g := New(root) - results, _, err := g.CreateTypes() + err := g.CreateTypes() + results := g.Structs + + // Output(os.Stderr, g, "test") if err != nil { t.Fatal("Failed to create structs: ", err) @@ -349,17 +376,17 @@ func TestArrayGeneration(t *testing.T) { } func TestNestedArrayGeneration(t *testing.T) { - root := &jsonschema.Schema{ + root := &Schema{ Title: "Favourite Bars", TypeValue: "object", - Properties: map[string]*jsonschema.Schema{ + Properties: map[string]*Schema{ "barName": {TypeValue: "string"}, "cities": { TypeValue: "array", - Items: &jsonschema.Schema{ + Items: &Schema{ Title: "City", TypeValue: "object", - Properties: map[string]*jsonschema.Schema{ + Properties: map[string]*Schema{ "name": {TypeValue: "string"}, "country": {TypeValue: "string"}, }, @@ -367,13 +394,18 @@ func TestNestedArrayGeneration(t *testing.T) { }, "tags": { TypeValue: "array", - Items: &jsonschema.Schema{TypeValue: "string"}, + Items: &Schema{TypeValue: "string"}, }, }, } + root.Init() + g := New(root) - results, _, err := g.CreateTypes() + err := g.CreateTypes() + results := g.Structs + + // Output(os.Stderr, g, "test") if err != nil { t.Error("Failed to create structs: ", err) @@ -424,23 +456,23 @@ func TestNestedArrayGeneration(t *testing.T) { } func TestMultipleSchemaStructGeneration(t *testing.T) { - root1 := &jsonschema.Schema{ + root1 := &Schema{ Title: "Root1Element", ID06: "http://example.com/schema/root1", - Properties: map[string]*jsonschema.Schema{ + Properties: map[string]*Schema{ "property1": {Reference: "root2#/definitions/address"}, }, } - root2 := &jsonschema.Schema{ + root2 := &Schema{ Title: "Root2Element", ID06: "http://example.com/schema/root2", - Properties: map[string]*jsonschema.Schema{ + Properties: map[string]*Schema{ "property1": {Reference: "#/definitions/address"}, }, - Definitions: map[string]*jsonschema.Schema{ + Definitions: map[string]*Schema{ "address": { - Properties: map[string]*jsonschema.Schema{ + Properties: map[string]*Schema{ "address1": {TypeValue: "string"}, "zip": {TypeValue: "number"}, }, @@ -448,8 +480,14 @@ func TestMultipleSchemaStructGeneration(t *testing.T) { }, } + root1.Init() + root2.Init() + g := New(root1, root2) - results, _, err := g.CreateTypes() + err := g.CreateTypes() + results := g.Structs + + // Output(os.Stderr, g, "test") if err != nil { t.Error("Failed to create structs: ", err) @@ -518,17 +556,22 @@ func TestThatJavascriptKeyNamesCanBeConvertedToValidGoNames(t *testing.T) { } func TestThatArraysWithoutDefinedItemTypesAreGeneratedAsEmptyInterfaces(t *testing.T) { - root := &jsonschema.Schema{} + root := &Schema{} root.Title = "Array without defined item" - root.Properties = map[string]*jsonschema.Schema{ + root.Properties = map[string]*Schema{ "name": {TypeValue: "string"}, "repositories": { TypeValue: "array", }, } + root.Init() + g := New(root) - results, _, err := g.CreateTypes() + err := g.CreateTypes() + results := g.Structs + + // Output(os.Stderr, g, "test") if err != nil { t.Errorf("Error generating structs: %v", err) @@ -550,14 +593,19 @@ func TestThatArraysWithoutDefinedItemTypesAreGeneratedAsEmptyInterfaces(t *testi } func TestThatTypesWithMultipleDefinitionsAreGeneratedAsEmptyInterfaces(t *testing.T) { - root := &jsonschema.Schema{} + root := &Schema{} root.Title = "Multiple possible types" - root.Properties = map[string]*jsonschema.Schema{ + root.Properties = map[string]*Schema{ "name": {TypeValue: []interface{}{"string", "integer"}}, } + root.Init() + g := New(root) - results, _, err := g.CreateTypes() + err := g.CreateTypes() + results := g.Structs + + // Output(os.Stderr, g, "test") if err != nil { t.Errorf("Error generating structs: %v", err) @@ -636,88 +684,37 @@ func TestThatUnmarshallingIsPossible(t *testing.T) { } } -func TestThatRootTypeKeyIsCorrectlyAssessed(t *testing.T) { - tests := []struct { - name string - input string - expected bool - }{ - { - name: "URL without fragment", - input: "http://example.com/schema", - expected: true, - }, - { - name: "URL with fragment", - input: "http://example.com/schema#/definitions/foo", - expected: false, - }, - { - name: "simple ID without fragment", - input: "/Test", - expected: true, - }, - { - name: "simple ID with fragment", - input: "/Test#/definitions/foo", - expected: false, - }, - { - name: "no ID", - input: "#", - expected: true, - }, - { - name: "empty", - input: "", - expected: true, - }, - } - - for _, test := range tests { - key, err := url.Parse(test.input) - if err != nil { - t.Fatal(err) - } - - actual := isRootSchemaKey(key) - if actual != test.expected { - t.Errorf("Test %q failed: for input %q, expected %t, got %t", test.name, test.input, test.expected, actual) - } - } -} - func TestTypeAliases(t *testing.T) { tests := []struct { gotype string - input *jsonschema.Schema + input *Schema structs, aliases int }{ { gotype: "string", - input: &jsonschema.Schema{TypeValue: "string"}, + input: &Schema{TypeValue: "string"}, structs: 0, aliases: 1, }, { gotype: "int", - input: &jsonschema.Schema{TypeValue: "integer"}, + input: &Schema{TypeValue: "integer"}, structs: 0, aliases: 1, }, { gotype: "bool", - input: &jsonschema.Schema{TypeValue: "boolean"}, + input: &Schema{TypeValue: "boolean"}, structs: 0, aliases: 1, }, { gotype: "[]*Foo", - input: &jsonschema.Schema{TypeValue: "array", - Items: &jsonschema.Schema{ + input: &Schema{TypeValue: "array", + Items: &Schema{ TypeValue: "object", Title: "foo", - Properties: map[string]*jsonschema.Schema{ + Properties: map[string]*Schema{ "nestedproperty": {TypeValue: "string"}, }, }}, @@ -726,24 +723,24 @@ func TestTypeAliases(t *testing.T) { }, { gotype: "[]interface{}", - input: &jsonschema.Schema{TypeValue: "array"}, + input: &Schema{TypeValue: "array"}, structs: 0, aliases: 1, }, { gotype: "map[string]string", - input: &jsonschema.Schema{ + input: &Schema{ TypeValue: "object", - AdditionalProperties: []*jsonschema.Schema{{TypeValue: "string"}}, + AdditionalProperties: (*AdditionalProperties)(&Schema{TypeValue: "string"}), }, structs: 0, aliases: 1, }, { gotype: "map[string]interface{}", - input: &jsonschema.Schema{ + input: &Schema{ TypeValue: "object", - AdditionalProperties: []*jsonschema.Schema{{TypeValue: []interface{}{"string", "integer"}}}, + AdditionalProperties: (*AdditionalProperties)(&Schema{TypeValue: []interface{}{"string", "integer"}}), }, structs: 0, aliases: 1, @@ -751,8 +748,15 @@ func TestTypeAliases(t *testing.T) { } for _, test := range tests { + test.input.Init() + g := New(test.input) - structs, aliases, err := g.CreateTypes() + err := g.CreateTypes() + structs := g.Structs + aliases := g.Aliases + + // Output(os.Stderr, g, "test") + if err != nil { t.Fatal(err) } diff --git a/input.go b/input.go new file mode 100644 index 0000000..3ce1e0b --- /dev/null +++ b/input.go @@ -0,0 +1,86 @@ +package generate + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/url" + "os" + "path" +) + +func ReadInputFiles(inputFiles []string) ([]*Schema, error) { + schemas := make([]*Schema, len(inputFiles)) + for i, file := range inputFiles { + b, err := ioutil.ReadFile(file) + if err != nil { + return nil, errors.New("failed to read the input file with error " + err.Error()) + } + + abPath, err := Abs(file) + if err != nil { + return nil, errors.New("failed to normalise input path with error " + err.Error()) + } + + fileURI := url.URL{ + Scheme: "file", + Path: abPath, + } + + schemas[i], err = Parse(string(b), &fileURI) + if err != nil { + if jsonError, ok := err.(*json.SyntaxError); ok { + line, character, lcErr := lineAndCharacter(b, int(jsonError.Offset)) + errStr := fmt.Sprintf("cannot parse JSON schema due to a syntax error at %s line %d, character %d: %v\n", file, line, character, jsonError.Error()) + if lcErr != nil { + errStr += fmt.Sprintf("couldn't find the line and character position of the error due to error %v\n", lcErr) + } + return nil, errors.New(errStr) + } + if jsonError, ok := err.(*json.UnmarshalTypeError); ok { + line, character, lcErr := lineAndCharacter(b, int(jsonError.Offset)) + errStr := fmt.Sprintf("the JSON type '%v' cannot be converted into the Go '%v' type on struct '%s', field '%v'. See input file %s line %d, character %d\n", jsonError.Value, jsonError.Type.Name(), jsonError.Struct, jsonError.Field, file, line, character) + if lcErr != nil { + errStr += fmt.Sprintf("couldn't find the line and character position of the error due to error %v\n", lcErr) + } + return nil, errors.New(errStr) + } + return nil, errors.New(fmt.Sprintf("failed to parse the input JSON schema file %s with error %v\n", file, err)) + } + } + + return schemas, nil +} + +func lineAndCharacter(bytes []byte, offset int) (line int, character int, err error) { + lf := byte(0x0A) + + if offset > len(bytes) { + return 0, 0, fmt.Errorf("couldn't find offset %d in %d bytes", offset, len(bytes)) + } + + // Humans tend to count from 1. + line = 1 + + for i, b := range bytes { + if b == lf { + line++ + character = 0 + } + character++ + if i == offset { + return line, character, nil + } + } + + return 0, 0, fmt.Errorf("couldn't find offset %d in %d bytes", offset, len(bytes)) +} + +func Abs(name string) (string, error) { + if path.IsAbs(name) { + return name, nil + } + wd, err := os.Getwd() + return path.Join(wd, name), err +} diff --git a/jsonschema.go b/jsonschema.go new file mode 100644 index 0000000..010bc1e --- /dev/null +++ b/jsonschema.go @@ -0,0 +1,309 @@ +package generate + +import ( + "encoding/json" + "errors" + "net/url" +) + +// AdditionalProperties handles additional properties present in the JSON schema. +type AdditionalProperties Schema + +// Schema represents JSON schema. +type Schema struct { + // SchemaType identifies the schema version. + // http://json-schema.org/draft-07/json-schema-core.html#rfc.section.7 + SchemaType string `json:"$schema"` + + // ID{04,06} is the schema URI identifier. + // http://json-schema.org/draft-07/json-schema-core.html#rfc.section.8.2 + ID04 string `json:"id"` // up to draft-04 + ID06 string `json:"$id"` // from draft-06 onwards + + // Title and Description state the intent of the schema. + Title string + Description string + + // TypeValue is the schema instance type. + // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6.1.1 + TypeValue interface{} `json:"type"` + + // Definitions are inline re-usable schemas. + // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.9 + Definitions map[string]*Schema + + // Properties, Required and AdditionalProperties describe an object's child instances. + // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6.5 + Properties map[string]*Schema + Required []string + + // "additionalProperties": {...} + AdditionalProperties *AdditionalProperties + + // "additionalProperties": false + AdditionalPropertiesBool *bool `json:"-"` + + AnyOf []*Schema + AllOf []*Schema + OneOf []*Schema + + // Default can be used to supply a default JSON value associated with a particular schema. + // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.10.2 + Default interface{} + + Examples []string + + // Reference is a URI reference to a schema. + // http://json-schema.org/draft-07/json-schema-core.html#rfc.section.8 + Reference string `json:"$ref"` + + // Items represents the types that are permitted in the array. + // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6.4 + Items *Schema + + // NameCount is the number of times the instance name was encountered across the schema. + NameCount int `json:"-" ` + + // Parent schema + Parent *Schema `json:"-" ` + + // Key of this schema i.e. { "JSONKey": { "type": "object", .... + JSONKey string `json:"-" ` + + // path element - for creating a path by traversing back to the root element + PathElement string `json:"-"` + + // calculated struct name of this object, cached here + GeneratedType string `json:"-"` +} + +// UnmarshalJSON handles unmarshalling AdditionalProperties from JSON. +func (ap *AdditionalProperties) UnmarshalJSON(data []byte) error { + var b bool + if err := json.Unmarshal(data, &b); err == nil { + *ap = (AdditionalProperties)(Schema{AdditionalPropertiesBool: &b}) + return nil + } + + // support anyOf, allOf, oneOf + a := map[string][]*Schema{} + if err := json.Unmarshal(data, &a); err == nil { + for k, v := range a { + switch k { + case "oneOf": + ap.OneOf = append(ap.OneOf, v...) + case "allOf": + ap.AllOf = append(ap.AllOf, v...) + case "anyOf": + ap.AnyOf = append(ap.AnyOf, v...) + } + } + return nil + } + + s := Schema{} + err := json.Unmarshal(data, &s) + if err == nil { + *ap = AdditionalProperties(s) + } + return err +} + +// ID returns the schema URI id. +func (schema *Schema) ID() string { + // prefer "$id" over "id" + if schema.ID06 == "" && schema.ID04 != "" { + return schema.ID04 + } + return schema.ID06 +} + +// Type returns the type which is permitted or an empty string if the type field is missing. +// The 'type' field in JSON schema also allows for a single string value or an array of strings. +// Examples: +// "a" => "a", false +// [] => "", false +// ["a"] => "a", false +// ["a", "b"] => "a", true +func (schema *Schema) Type() (firstOrDefault string, multiple bool) { + // We've got a single value, e.g. { "type": "object" } + if ts, ok := schema.TypeValue.(string); ok { + firstOrDefault = ts + multiple = false + return + } + + // We could have multiple types in the type value, e.g. { "type": [ "object", "array" ] } + if a, ok := schema.TypeValue.([]interface{}); ok { + multiple = len(a) > 1 + for _, n := range a { + if s, ok := n.(string); ok { + firstOrDefault = s + return + } + } + } + + return "", multiple +} + +// returns "type" as an array +func (schema *Schema) MultiType() ([]string, bool) { + // We've got a single value, e.g. { "type": "object" } + if ts, ok := schema.TypeValue.(string); ok { + return []string{ts}, false + } + + // We could have multiple types in the type value, e.g. { "type": [ "object", "array" ] } + if a, ok := schema.TypeValue.([]interface{}); ok { + rv := []string{} + for _, n := range a { + if s, ok := n.(string); ok { + rv = append(rv, s) + } + } + return rv, len(rv) > 1 + } + + return nil, false +} + +func (schema *Schema) GetRoot() *Schema { + if schema.Parent != nil { + return schema.Parent.GetRoot() + } else { + return schema + } +} + +// Parse parses a JSON schema from a string. +func Parse(schema string, uri *url.URL) (*Schema, error) { + s := &Schema{} + err := json.Unmarshal([]byte(schema), s) + + if err != nil { + return s, err + } + + if s.ID() == "" { + s.ID06 = uri.String() + } + + // validate root URI, it MUST be an absolute URI + if abs, err := url.Parse(s.ID()); err != nil { + return nil, errors.New("error parsing $id of document \"" + uri.String() + "\": " + err.Error()) + } else { + if !abs.IsAbs() { + return nil, errors.New("$id of document not absolute URI: \"" + uri.String() + "\": \"" + s.ID() + "\"") + } + } + + s.Init() + + return s, nil +} + +// public for testing.... TODO consider making generator and jsonschema a single package +func (schema *Schema) Init() { + root := schema.GetRoot() + root.updateParentLinks() + root.ensureSchemaKeyword() + root.updatePathElements() +} + +func (schema *Schema) updatePathElements() { + if schema.IsRoot() { + schema.PathElement = "#" + } + + for k, d := range schema.Definitions { + d.PathElement = "definitions/" + k + d.updatePathElements() + } + + for k, p := range schema.Properties { + p.PathElement = "properties/" + k + p.updatePathElements() + } + + if schema.AdditionalProperties != nil { + schema.AdditionalProperties.PathElement = "additionalProperties" + (*Schema)(schema.AdditionalProperties).updatePathElements() + } + + if schema.Items != nil { + schema.Items.PathElement = "items" + schema.Items.updatePathElements() + } +} + +func (schema *Schema) updateParentLinks() { + for k, d := range schema.Definitions { + d.JSONKey = k + d.Parent = schema + d.updateParentLinks() + } + + for k, p := range schema.Properties { + p.JSONKey = k + p.Parent = schema + p.updateParentLinks() + } + if schema.AdditionalProperties != nil { + schema.AdditionalProperties.Parent = schema + (*Schema)(schema.AdditionalProperties).updateParentLinks() + } + if schema.Items != nil { + schema.Items.Parent = schema + schema.Items.updateParentLinks() + } +} + +func (schema *Schema) ensureSchemaKeyword() error { + check := func(k string, s *Schema) error { + if s.SchemaType != "" { + return errors.New("invalid $schema keyword: " + k) + } else { + return s.ensureSchemaKeyword() + } + } + for k, d := range schema.Definitions { + if err := check(k, d); err != nil { + return err + } + } + for k, d := range schema.Properties { + if err := check(k, d); err != nil { + return err + } + } + if schema.AdditionalProperties != nil { + if err := check("additionalProperties", (*Schema)(schema.AdditionalProperties)); err != nil { + return err + } + } + if schema.Items != nil { + if err := check("items", schema.Items); err != nil { + return err + } + } + return nil +} + +// backwards compatible: guess the users intention when they didn't specify a type... +func (schema *Schema) FixMissingTypeValue() { + if schema.TypeValue == nil { + if schema.Reference == "" && len(schema.Properties) > 0 { + schema.TypeValue = "object" + return + } + if schema.Items != nil { + schema.TypeValue = "array" + return + } + } +} + +func (schema *Schema) IsRoot() bool { + return schema.Parent == nil +} diff --git a/jsonschema/jsonschema.go b/jsonschema/jsonschema.go deleted file mode 100644 index 93412d3..0000000 --- a/jsonschema/jsonschema.go +++ /dev/null @@ -1,244 +0,0 @@ -// Package jsonschema provides primitives for extracting data from JSON schemas. -package jsonschema - -import ( - "encoding/json" - "errors" - "strings" -) - -// Schema represents JSON schema. -type Schema struct { - // SchemaType identifies the schema version. - // http://json-schema.org/draft-07/json-schema-core.html#rfc.section.7 - SchemaType string `json:"$schema"` - - // ID{04,06} is the schema URI identifier. - // http://json-schema.org/draft-07/json-schema-core.html#rfc.section.8.2 - ID04 string `json:"id"` // up to draft-04 - ID06 string `json:"$id"` // from draft-06 onwards - - // Title and Description state the intent of the schema. - Title string - Description string - - // TypeValue is the schema instance type. - // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6.1.1 - TypeValue interface{} `json:"type"` - - // Definitions are inline re-usable schemas. - // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.9 - Definitions map[string]*Schema - - // Properties, Required and AdditionalProperties describe an object's child instances. - // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6.5 - Properties map[string]*Schema - Required []string - AdditionalProperties AdditionalProperties - - // Default can be used to supply a default JSON value associated with a particular schema. - // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.10.2 - Default interface{} - - // Reference is a URI reference to a schema. - // http://json-schema.org/draft-07/json-schema-core.html#rfc.section.8 - Reference string `json:"$ref"` - - // Items represents the types that are permitted in the array. - // http://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6.4 - Items *Schema - - // NameCount is the number of times the instance name was encountered accross the schema. - NameCount int `json:"-" ` -} - -// ID returns the schema URI id. -func (s *Schema) ID() string { - // prefer "$id" over "id" - if s.ID06 == "" && s.ID04 != "" { - return s.ID04 - } - return s.ID06 -} - -// Type returns the type which is permitted or an empty string if the type field is missing. -// The 'type' field in JSON schema also allows for a single string value or an array of strings. -// Examples: -// "a" => "a", false -// [] => "", false -// ["a"] => "a", false -// ["a", "b"] => "a", true -func (s *Schema) Type() (firstOrDefault string, multiple bool) { - // We've got a single value, e.g. { "type": "object" } - if ts, ok := s.TypeValue.(string); ok { - firstOrDefault = ts - multiple = false - return - } - - // We could have multiple types in the type value, e.g. { "type": [ "object", "array" ] } - if a, ok := s.TypeValue.([]interface{}); ok { - multiple = len(a) > 1 - for _, n := range a { - if s, ok := n.(string); ok { - firstOrDefault = s - return - } - } - } - - return "", multiple -} - -// Parse parses a JSON schema from a string. -func Parse(schema string) (*Schema, error) { - s := &Schema{} - err := json.Unmarshal([]byte(schema), s) - - if err != nil { - return s, err - } - - if s.SchemaType == "" { - return s, errors.New("JSON schema must have a $schema key") - } - - return s, err -} - -// ExtractTypes creates a map of defined types within the schema. -func (s *Schema) ExtractTypes() map[string]*Schema { - types := make(map[string]*Schema) - - addTypeAndChildrenToMap("#", "", s, types) - - counts := make(map[string]int) - for path, t := range types { - parts := strings.Split(path, "/") - name := parts[len(parts)-1] - counts[name] = counts[name] + 1 - t.NameCount = counts[name] - } - - return types -} - -func addTypeAndChildrenToMap(path string, name string, s *Schema, types map[string]*Schema) { - t, multiple := s.Type() - if multiple { - // If we have more than one possible type for this field, the result is an interface{} in the struct definition. - return - } - - // Add root schemas composed of an object type without property. - // The resulting type depends on the presence of additionalProperties. - if (t == "object" || t == "") && len(s.Properties) == 0 && path == "#" { - types[path] = s - return - } - - // Add root schemas composed only of a simple type - if !(t == "object" || t == "") && path == "#" { - types[path] = s - } - - if t == "array" { - if s.Items != nil { - if path == "#" { - path += "/arrayitems" - } - addTypeAndChildrenToMap(path, name, s.Items, types) - } - return - } - - namePrefix := "/" + name - // Don't add the name into the root, or we end up with an extra slash. - if (path == "#" || path == "#/arrayitems") && name == "" { - namePrefix = "" - } - - if len(s.Properties) == 0 && len(s.AdditionalProperties) > 0 { - // if we have more than one valid type in additionalProperties, we can disregard them - // as we will render as a weakly-typed map i.e map[string]interface{} - if len(s.AdditionalProperties) == 1 { - addTypeAndChildrenToMap(path, name, s.AdditionalProperties[0], types) - } - return - } - - if len(s.Properties) > 0 || t == "object" { - types[path+namePrefix] = s - } - - if s.Definitions != nil { - for k, d := range s.Definitions { - addTypeAndChildrenToMap(path+namePrefix+"/definitions", k, d, types) - } - } - - if s.Properties != nil { - for k, d := range s.Properties { - // Only add the children as their own type if they have properties at all. - addTypeAndChildrenToMap(path+namePrefix+"/properties", k, d, types) - } - } -} - -// ListReferences lists all of the references in a schema. -func (s *Schema) ListReferences() map[string]bool { - m := make(map[string]bool) - addReferencesToMap(s, m) - return m -} - -func addReferencesToMap(s *Schema, m map[string]bool) { - if s.Reference != "" { - m[s.Reference] = true - } - - if s.Definitions != nil { - for _, d := range s.Definitions { - addReferencesToMap(d, m) - } - } - - if s.Properties != nil { - for _, p := range s.Properties { - addReferencesToMap(p, m) - } - } - - if s.Items != nil { - addReferencesToMap(s.Items, m) - } -} - -// AdditionalProperties handles additional properties present in the JSON schema. -type AdditionalProperties []*Schema - -// UnmarshalJSON handles unmarshalling AdditionalProperties from JSON. -func (ap *AdditionalProperties) UnmarshalJSON(data []byte) error { - var b bool - if err := json.Unmarshal(data, &b); err == nil { - return nil - } - - // support anyOf, allOf, oneOf - a := map[string][]*Schema{} - if err := json.Unmarshal(data, &a); err == nil { - for k, v := range a { - if k == "oneOf" || k == "allOf" || k == "anyOf" { - *ap = append(*ap, v...) - } - } - return nil - } - - s := Schema{} - err := json.Unmarshal(data, &s) - if err == nil { - *ap = append(*ap, &s) - } - return err -} diff --git a/jsonschema/jsonschemaparse_test.go b/jsonschema/jsonschemaparse_test.go deleted file mode 100644 index 770af15..0000000 --- a/jsonschema/jsonschemaparse_test.go +++ /dev/null @@ -1,595 +0,0 @@ -package jsonschema - -import ( - "strings" - "testing" -) - -func TestThatAMissingSchemaKeyResultsInAnError(t *testing.T) { - invalid := `{ - "title": "root" - }` - - _, invaliderr := Parse(invalid) - - valid := `{ - "$schema": "http://json-schema.org/schema#", - "title": "root" - }` - - _, validerr := Parse(valid) - - if invaliderr == nil { - t.Error("When the $schema key is missing from the root, the JSON Schema is not valid") - } - - if validerr != nil { - t.Error("It should be possible to parse a simple JSON schema if the $schema key is present") - } -} - -func TestThatTheRootSchemaCanBeParsed(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/schema#", - "title": "root" - }` - so, err := Parse(s) - - if err != nil { - t.Fatal("It should be possible to unmarshal a simple schema, but received error:", err) - } - - if so.Title != "root" { - t.Errorf("The title was not deserialised from the JSON schema, expected %s, but got %s", "root", so.Title) - } -} - -func TestThatPropertiesCanBeParsed(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/schema#", - "title": "root", - "properties": { - "name": { - "type": "string" - }, - "address": { - "$ref": "#/definitions/address" - }, - "status": { - "$ref": "#/definitions/status" - } - } - }` - so, err := Parse(s) - - if err != nil { - t.Fatal("It was not possible to unmarshal the schema:", err) - } - - nameType, nameMultiple := so.Properties["name"].Type() - if nameType != "string" || nameMultiple { - t.Errorf("expected property 'name' type to be 'string', but was '%v'", nameType) - } - - addressType, _ := so.Properties["address"].Type() - if addressType != "" { - t.Errorf("expected property 'address' type to be '', but was '%v'", addressType) - } - - if so.Properties["address"].Reference != "#/definitions/address" { - t.Errorf("expected property 'address' reference to be '#/definitions/address', but was '%v'", so.Properties["address"].Reference) - } - - if so.Properties["status"].Reference != "#/definitions/status" { - t.Errorf("expected property 'status' reference to be '#/definitions/status', but was '%v'", so.Properties["status"].Reference) - } -} - -func TestThatStructTypesCanBeExtracted(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "Example", - "definitions": { - "address": { - "properties": { - "houseName": { "type": "string" }, - "postcode": { "type": "string" } - } - }, - "status": { - "properties": { - "favouritecat": { - "enum": [ "A", "B", "C", "D", "E", "F" ], - "type": "string" - } - } - }, - "links": { - "type": "array", - "items": { - "type": "object", - "properties": { - "asset_id": { - "type": "string" - } - } - } - } - }, - "properties": { - "address": { "$ref": "#/definitions/address" } - } - }` - so, err := Parse(s) - - if err != nil { - t.Fatal("Failed to parse the test JSON: ", err) - } - - // Check that the definitions have been unmarshalled correctly into a map. - if len(so.Definitions) != 3 { - t.Errorf("The parsed schema should have two child definitions, one for address, one for status, and one for links but got %s", - strings.Join(getKeyNames(so.Definitions), ", ")) - } - - // Check that the types can be extracted into a map. - types := so.ExtractTypes() - - if len(types) != 4 { - t.Errorf("Expected 4 types, the example, address, status and links, but got %d types - %s", len(types), - strings.Join(getKeyNames(types), ", ")) - } - - // Check that the keys of the types map to expected references. - if _, ok := types["#/definitions/address"]; !ok { - t.Errorf("Expecting to find the address type in the map under key #/definitions/address, available keys were %s", - strings.Join(getKeyNames(types), ", ")) - } - - if _, ok := types["#/definitions/links"]; !ok { - t.Errorf("Expected to find the links type in the map under key #/definitions/links, available keys were %s", - strings.Join(getKeyNames(types), ", ")) - } -} - -func TestThatAliasTypesCanBeExtracted(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "Example", - "type": "string", - "enum": [ "A", "B", "C", "D", "E", "F" ] - }` - so, err := Parse(s) - - if err != nil { - t.Fatal("Failed to parse the test JSON: ", err) - } - - // Check that the types can be extracted into a map. - types := so.ExtractTypes() - - if len(types) != 1 { - t.Errorf("Expected 1 type, the Root type, but got %d types - %s", len(types), - strings.Join(getKeyNames(types), ", ")) - } - - // Check that the key of the type maps to expected reference. - if _, ok := types["#"]; !ok { - t.Errorf("Expected to find the Root type in the map under key #, available keys were %s", - strings.Join(getKeyNames(types), ", ")) - } -} - -func TestThatNestedTypesCanBeExtracted(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "Example", - "properties": { - "favouritecat": { - "enum": [ "A", "B", "C", "D", "E", "F" ], - "type": "string" - }, - "subobject": { - "type": "object", - "properties": { - "subproperty1": { - "type": "date" - } - } - } - } - }` - so, err := Parse(s) - - if err != nil { - t.Error("failed to parse the test JSON: ", err) - } - - // Check that the types can be extracted into a map. - types := so.ExtractTypes() - - if len(types) != 2 { - t.Errorf("expected 2 types, the example and subobject, but got %d types - %s", len(types), - strings.Join(getKeyNames(types), ", ")) - } - - // Check that the names of the types map to expected references. - if _, ok := types["#/properties/subobject"]; !ok { - t.Errorf("was expecting to find the subobject type in the map under key #/properties/subobject, available types were '%s'", - strings.Join(getKeyNames(types), ", ")) - } -} - -func getKeyNames(m map[string]*Schema) []string { - keys := []string{} - for k := range m { - keys = append(keys, k) - } - return keys -} - -func TestThatAdditionalPropertiesCanBeExtracted(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "Example", - "properties": { - "subobject1": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "subobject2": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "x": { "type": "string" } - } - } - }, - "subobject3": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "properties": { - "x": { "type": "string" } - } - } - ], - "allOf": [ - { - "type": "object", - "properties": { - "x": { "type": "string" } - } - } - ], - "oneOf": [ - { - "type": "object", - "properties": { - "x": { "type": "string" } - } - } - ], - "not": [ - { - "type": "object", - "properties": { - "x": { "type": "string" } - } - } - ] - } - } - } - }` - so, err := Parse(s) - - if err != nil { - t.Error("failed to parse the test JSON: ", err) - } - - if len(so.Properties["subobject1"].AdditionalProperties) != 1 { - t.Error("expected 1 schemas in subobject3") - } - - if len(so.Properties["subobject2"].AdditionalProperties) != 1 { - t.Error("expected 1 schemas in subobject3") - } - - if len(so.Properties["subobject3"].AdditionalProperties) != 3 { - t.Error("expected 3 schemas in subobject3") - } - - // Check that the types can be extracted into a map. - types := so.ExtractTypes() - - if len(types) != 2 { - t.Errorf("expected 2 types, the example and subobject, but got %d types - %s", len(types), - strings.Join(getKeyNames(types), ", ")) - } - - // Check that the names of the types map to expected references. - if _, ok := types["#/properties/subobject2"]; !ok { - t.Errorf("was expecting to find the subobject2 type in the map under key #/properties/subobject3, available types were %s", - strings.Join(getKeyNames(types), ", ")) - } -} - -func TestThatArraysAreSupported(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "ProductSet", - "type": "array", - "items": { - "title": "Product", - "type": "object", - "properties": { - "id": { - "description": "The unique identifier for a product", - "type": "number" - }, - "name": { - "type": "string" - }, - "price": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - } - }, - "required": ["id", "name", "price"] - } - }` - - so, err := Parse(s) - - if err != nil { - t.Error("failed to parse the test JSON: ", err) - } - - // Check that the types can be extracted into a map. - types := so.ExtractTypes() - - if len(types) != 2 { - t.Errorf("Expected 2 types, ProductSet and Product, but got %d types - %s", len(types), - strings.Join(getKeyNames(types), ", ")) - } - - // Check that the keys of the types map to expected references. - pss, ok := types["#"] - if !ok { - t.Errorf("Expected to find the '#' schema path, but available paths were %s", - strings.Join(getKeyNames(types), ", ")) - } - ps, ok := types["#/arrayitems"] - if !ok { - t.Errorf("Expected to find the '#/arrayitems' schema path, but available paths were %s", - strings.Join(getKeyNames(types), ", ")) - } - - if pss.Title != "ProductSet" { - t.Errorf("Expected the root schema's title to be 'ProductSet', but it was %s", pss.Title) - } - - if ps.Title != "Product" { - t.Errorf("Expected the array item's title to be 'Product', but it was %s", ps.Title) - } - - if len(ps.Properties) != 4 { - t.Errorf("Expected the Product schema to have 4 properties, but it had %d", len(ps.Properties)) - } - - tagType, _ := ps.Properties["tags"].Type() - if tagType != "array" { - t.Errorf("Expected the Tags property type to be 'array', but it was %s", tagType) - } -} - -func TestThatReferencesCanBeListed(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Product set", - "type": "array", - "definitions": { - "address": { - "properties": { - "houseName": { "type": "string" }, - "postcode": { "type": "string" } - } - } - }, - "items": { - "title": "Product", - "type": "object", - "properties": { - "id": { - "description": "The unique identifier for a product", - "type": "number" - }, - "name": { - "type": "string" - }, - "price": { - "type": "number", - "minimum": 0, - "exclusiveMinimum": true - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - }, - "dimensions": { - "$ref": "#/definitions/address" - }, - "warehouseLocation": { - "description": "Coordinates of the warehouse with the product", - "$ref": "http://json-schema.org/geo" - } - }, - "required": ["id", "name", "price"] - } -}` - so, err := Parse(s) - - if err != nil { - t.Error("failed to parse the test JSON: ", err) - } - - refs := so.ListReferences() - - if len(refs) != 2 { - t.Errorf("Expected 1 references, one internal, one external, but got %d references", len(refs)) - } - - if _, ok := refs["http://json-schema.org/geo"]; !ok { - t.Error("Couldn't find the reference to http://json-schema.org/geo") - } - - if _, ok := refs["#/definitions/address"]; !ok { - t.Error("Couldn't find the reference to #/definitions/address") - } -} - -func TestThatRequiredPropertiesAreIncludedInTheSchemaModel(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/draft-04/schema#", - "name": "Repository Configuration", - "type": "object", - "additionalProperties": false, - "required": [ "name" ], - "properties": { - "name": { - "type": "string", - "description": "Repository name." - }, - "repositories": { - "type": "string", - "description": "A set of additional repositories where packages can be found.", - "additionalProperties": true - } - } -}` - so, err := Parse(s) - - if err != nil { - t.Fatal("Failed to parse the test JSON: ", err) - } - - types := so.ExtractTypes() - - if len(types) != 1 { - t.Errorf("Expected just the Repository Configuration type to be extracted, but got %d types extracted", len(types)) - } - - var rc *Schema - var ok bool - if rc, ok = types["#"]; !ok { - t.Fatalf("Couldn't find the reference to the Repository Configuration root type, the types found were %+v", getKeyNames(types)) - } - - if len(rc.Required) != 1 || rc.Required[0] != "name" { - t.Errorf("Expected the required field of the Repository Configuration type to contain a reference to 'name'.") - } -} - -func TestThatPropertiesCanHaveMultipleTypes(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/schema#", - "title": "root", - "properties": { - "name": { - "type": [ "integer", "string" ] - } - } - }` - so, err := Parse(s) - - if err != nil { - t.Fatal("It was not possible to unmarshal the schema:", err) - } - - nameType, nameMultiple := so.Properties["name"].Type() - if nameType != "integer" { - t.Errorf("expected first value of property 'name' type to be 'integer', but was '%v'", nameType) - } - - if !nameMultiple { - t.Errorf("expected multiple types, but only returned one") - } -} - -func TestThatParsingInvalidValuesReturnsAnError(t *testing.T) { - s := `{ " }` - _, err := Parse(s) - - if err == nil { - t.Fatal("Expected a parsing error, but got nil") - } -} - -func TestThatDefaultsCanBeParsed(t *testing.T) { - s := `{ - "$schema": "http://json-schema.org/schema#", - "title": "root", - "properties": { - "name": { - "type": [ "integer", "string" ], - "default":"Enrique" - } - } - }` - so, err := Parse(s) - - if err != nil { - t.Fatal("It was not possible to unmarshal the schema:", err) - } - - defaultValue := so.Properties["name"].Default - if defaultValue != "Enrique" { - t.Errorf("expected default value of property 'name' type to be 'Enrique', but was '%v'", defaultValue) - } -} - -func TestReturnedSchemaId(t *testing.T) { - tests := []struct { - input *Schema - expected string - }{ - { - input: &Schema{}, - expected: "", - }, - { - input: &Schema{ID06: "http://example.com/foo.json", ID04: "#foo"}, - expected: "http://example.com/foo.json", - }, - { - input: &Schema{ID04: "#foo"}, - expected: "#foo", - }, - } - - for idx, test := range tests { - actual := test.input.ID() - if actual != test.expected { - t.Errorf("Test %d failed: For input \"%+v\", expected \"%s\", got \"%s\"", idx, test.input, test.expected, actual) - } - } -} diff --git a/jsonschemaparse_test.go b/jsonschemaparse_test.go new file mode 100644 index 0000000..65eccae --- /dev/null +++ b/jsonschemaparse_test.go @@ -0,0 +1,172 @@ +package generate + +import ( + "net/url" + "testing" +) + +func TestThatAMissingSchemaKeyResultsInAnError(t *testing.T) { + invalid := `{ + "title": "root" + }` + + _, invaliderr := Parse(invalid, &url.URL{Scheme: "file", Path: "jsonschemaparse_test.go"}) + + valid := `{ + "$schema": "http://json-schema.org/schema#", + "title": "root" + }` + + _, validerr := Parse(valid, &url.URL{Scheme: "file", Path: "jsonschemaparse_test.go"}) + + if invaliderr == nil { + // it SHOULD be used in the root schema + // t.Error("When the $schema key is missing from the root, the JSON Schema is not valid") + } + + if validerr != nil { + t.Error("It should be possible to parse a simple JSON schema if the $schema key is present") + } +} + +func TestThatTheRootSchemaCanBeParsed(t *testing.T) { + s := `{ + "$schema": "http://json-schema.org/schema#", + "title": "root" + }` + so, err := Parse(s, &url.URL{Scheme: "file", Path: "jsonschemaparse_test.go"}) + + if err != nil { + t.Fatal("It should be possible to unmarshal a simple schema, but received error:", err) + } + + if so.Title != "root" { + t.Errorf("The title was not deserialised from the JSON schema, expected %s, but got %s", "root", so.Title) + } +} + +func TestThatPropertiesCanBeParsed(t *testing.T) { + s := `{ + "$schema": "http://json-schema.org/schema#", + "title": "root", + "properties": { + "name": { + "type": "string" + }, + "address": { + "$ref": "#/definitions/address" + }, + "status": { + "$ref": "#/definitions/status" + } + } + }` + so, err := Parse(s, &url.URL{Scheme: "file", Path: "jsonschemaparse_test.go"}) + + if err != nil { + t.Fatal("It was not possible to unmarshal the schema:", err) + } + + nameType, nameMultiple := so.Properties["name"].Type() + if nameType != "string" || nameMultiple { + t.Errorf("expected property 'name' type to be 'string', but was '%v'", nameType) + } + + addressType, _ := so.Properties["address"].Type() + if addressType != "" { + t.Errorf("expected property 'address' type to be '', but was '%v'", addressType) + } + + if so.Properties["address"].Reference != "#/definitions/address" { + t.Errorf("expected property 'address' reference to be '#/definitions/address', but was '%v'", so.Properties["address"].Reference) + } + + if so.Properties["status"].Reference != "#/definitions/status" { + t.Errorf("expected property 'status' reference to be '#/definitions/status', but was '%v'", so.Properties["status"].Reference) + } +} + +func TestThatPropertiesCanHaveMultipleTypes(t *testing.T) { + s := `{ + "$schema": "http://json-schema.org/schema#", + "title": "root", + "properties": { + "name": { + "type": [ "integer", "string" ] + } + } + }` + so, err := Parse(s, &url.URL{Scheme: "file", Path: "jsonschemaparse_test.go"}) + + if err != nil { + t.Fatal("It was not possible to unmarshal the schema:", err) + } + + nameType, nameMultiple := so.Properties["name"].Type() + if nameType != "integer" { + t.Errorf("expected first value of property 'name' type to be 'integer', but was '%v'", nameType) + } + + if !nameMultiple { + t.Errorf("expected multiple types, but only returned one") + } +} + +func TestThatParsingInvalidValuesReturnsAnError(t *testing.T) { + s := `{ " }` + _, err := Parse(s, &url.URL{Scheme: "file", Path: "jsonschemaparse_test.go"}) + + if err == nil { + t.Fatal("Expected a parsing error, but got nil") + } +} + +func TestThatDefaultsCanBeParsed(t *testing.T) { + s := `{ + "$schema": "http://json-schema.org/schema#", + "title": "root", + "properties": { + "name": { + "type": [ "integer", "string" ], + "default":"Enrique" + } + } + }` + so, err := Parse(s, &url.URL{Scheme: "file", Path: "jsonschemaparse_test.go"}) + + if err != nil { + t.Fatal("It was not possible to unmarshal the schema:", err) + } + + defaultValue := so.Properties["name"].Default + if defaultValue != "Enrique" { + t.Errorf("expected default value of property 'name' type to be 'Enrique', but was '%v'", defaultValue) + } +} + +func TestReturnedSchemaId(t *testing.T) { + tests := []struct { + input *Schema + expected string + }{ + { + input: &Schema{}, + expected: "", + }, + { + input: &Schema{ID06: "http://example.com/foo.json", ID04: "#foo"}, + expected: "http://example.com/foo.json", + }, + { + input: &Schema{ID04: "#foo"}, + expected: "#foo", + }, + } + + for idx, test := range tests { + actual := test.input.ID() + if actual != test.expected { + t.Errorf("Test %d failed: For input \"%+v\", expected \"%s\", got \"%s\"", idx, test.input, test.expected, actual) + } + } +} diff --git a/output.go b/output.go new file mode 100644 index 0000000..5bf54be --- /dev/null +++ b/output.go @@ -0,0 +1,294 @@ +package generate + +import ( + "bytes" + "fmt" + "io" + "sort" + "strings" +) + +func getOrderedFieldNames(m map[string]Field) []string { + keys := make([]string, len(m)) + idx := 0 + for k := range m { + keys[idx] = k + idx++ + } + sort.Strings(keys) + return keys +} + +func getOrderedStructNames(m map[string]Struct) []string { + keys := make([]string, len(m)) + idx := 0 + for k := range m { + keys[idx] = k + idx++ + } + sort.Strings(keys) + return keys +} + +// generate code and write to w +func Output(w io.Writer, g *Generator, pkg string) { + structs := g.Structs + aliases := g.Aliases + + fmt.Fprintln(w, "// Code generated by schema-generate. DO NOT EDIT.") + fmt.Fprintln(w) + fmt.Fprintf(w, "package %v\n", cleanPackageName(pkg)) + + // write all the code into a buffer, compiler functions will return list of imports + // write list of imports into main output stream, followed by the code + codeBuf := new(bytes.Buffer) + imports := make(map[string]bool) + + for _, k := range getOrderedStructNames(structs) { + s := structs[k] + if s.GenerateCode { + emitMarshalCode(codeBuf, s, imports) + emitUnmarshalCode(codeBuf, s, imports) + } + } + + if len(imports) > 0 { + fmt.Fprintf(w, "\nimport (\n") + for k := range imports { + fmt.Fprintf(w, " \"%s\"\n", k) + } + fmt.Fprintf(w, ")\n") + } + + for _, k := range getOrderedFieldNames(aliases) { + a := aliases[k] + + fmt.Fprintln(w, "") + fmt.Fprintf(w, "// %s\n", a.Name) + fmt.Fprintf(w, "type %s %s\n", a.Name, a.Type) + } + + for _, k := range getOrderedStructNames(structs) { + s := structs[k] + + fmt.Fprintln(w, "") + outputNameAndDescriptionComment(s.Name, s.Description, w) + fmt.Fprintf(w, "type %s struct {\n", s.Name) + + for _, fieldKey := range getOrderedFieldNames(s.Fields) { + f := s.Fields[fieldKey] + + // Only apply omitempty if the field is not required. + omitempty := ",omitempty" + if f.Required { + omitempty = "" + } + + if f.Description != "" { + outputFieldDescriptionComment(f.Description, w) + } + + fmt.Fprintf(w, " %s %s `json:\"%s%s\"`\n", f.Name, f.Type, f.JSONName, omitempty) + } + + fmt.Fprintln(w, "}") + } + + // write code after structs for clarity + w.Write(codeBuf.Bytes()) +} + +func emitMarshalCode(w io.Writer, s Struct, imports map[string]bool) { + imports["bytes"] = true + fmt.Fprintf(w, + ` +func (strct *%s) MarshalJSON() ([]byte, error) { + buf := bytes.NewBuffer(make([]byte, 0)) + buf.WriteString("{") +`, s.Name) + + if len(s.Fields) > 0 { + fmt.Fprintf(w, " comma := false\n") + // Marshal all the defined fields + for _, fieldKey := range getOrderedFieldNames(s.Fields) { + f := s.Fields[fieldKey] + if f.JSONName == "-" { + continue + } + if f.Required { + fmt.Fprintf(w, " // \"%s\" field is required\n", f.Name) + // currently only objects are supported + if strings.HasPrefix(f.Type, "*") { + imports["errors"] = true + fmt.Fprintf(w, ` if strct.%s == nil { + return nil, errors.New("%s is a required field") + } +`, f.Name, f.JSONName) + } else { + fmt.Fprintf(w, " // only required object types supported for marshal checking (for now)\n") + } + } + + fmt.Fprintf(w, + ` // Marshal the "%[1]s" field + if comma { + buf.WriteString(",") + } + buf.WriteString("\"%[1]s\": ") + if tmp, err := json.Marshal(strct.%[2]s); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + comma = true +`, f.JSONName, f.Name) + } + } + if s.AdditionalType != "" { + if s.AdditionalType != "false" { + imports["fmt"] = true + + if len(s.Fields) == 0 { + fmt.Fprintf(w, " comma := false\n") + } + + fmt.Fprintf(w, " // Marshal any additional Properties\n") + // Marshal any additional Properties + fmt.Fprintf(w, ` for k, v := range strct.AdditionalProperties { + if comma { + buf.WriteString(",") + } + buf.WriteString(fmt.Sprintf("\"%%s\":", k)) + if tmp, err := json.Marshal(v); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + comma = true + } +`) + } + } + + fmt.Fprintf(w, ` + buf.WriteString("}") + rv := buf.Bytes() + return rv, nil +} +`) +} + +func emitUnmarshalCode(w io.Writer, s Struct, imports map[string]bool) { + imports["encoding/json"] = true + // unmarshal code + fmt.Fprintf(w, ` +func (strct *%s) UnmarshalJSON(b []byte) error { +`, s.Name) + // setup required bools + for _, fieldKey := range getOrderedFieldNames(s.Fields) { + f := s.Fields[fieldKey] + if f.Required { + fmt.Fprintf(w, " %sReceived := false\n", f.JSONName) + } + } + // setup initial unmarshal + fmt.Fprintf(w, ` var jsonMap map[string]json.RawMessage + if err := json.Unmarshal(b, &jsonMap); err != nil { + return err + }`) + + // figure out if we need the "v" output of the range keyword + needVal := "_" + if len(s.Fields) > 0 || s.AdditionalType != "false" { + needVal = "v" + } + // start the loop + fmt.Fprintf(w, ` + // parse all the defined properties + for k, %s := range jsonMap { + switch k { +`, needVal) + // handle defined properties + for _, fieldKey := range getOrderedFieldNames(s.Fields) { + f := s.Fields[fieldKey] + if f.JSONName == "-" { + continue + } + fmt.Fprintf(w, ` case "%s": + if err := json.Unmarshal([]byte(v), &strct.%s); err != nil { + return err + } +`, f.JSONName, f.Name) + if f.Required { + fmt.Fprintf(w, " %sReceived = true\n", f.JSONName) + } + } + + // handle additional property + if s.AdditionalType != "" { + if s.AdditionalType == "false" { + // all unknown properties are not allowed + imports["fmt"] = true + fmt.Fprintf(w, ` default: + return fmt.Errorf("additional property not allowed: \"" + k + "\"") +`) + } else { + fmt.Fprintf(w, ` default: + // an additional "%s" value + var additionalValue %s + if err := json.Unmarshal([]byte(v), &additionalValue); err != nil { + return err // invalid additionalProperty + } + if strct.AdditionalProperties == nil { + strct.AdditionalProperties = make(map[string]%s, 0) + } + strct.AdditionalProperties[k]= additionalValue +`, s.AdditionalType, s.AdditionalType, s.AdditionalType) + } + } + fmt.Fprintf(w, " }\n") // switch + fmt.Fprintf(w, " }\n") // for + + // check all Required fields were received + for _, fieldKey := range getOrderedFieldNames(s.Fields) { + f := s.Fields[fieldKey] + if f.Required { + imports["errors"] = true + fmt.Fprintf(w, ` // check if %s (a required property) was received + if !%sReceived { + return errors.New("\"%s\" is required but was not present") + } +`, f.JSONName, f.JSONName, f.JSONName) + } + } + + fmt.Fprintf(w, " return nil\n") + fmt.Fprintf(w, "}\n") // UnmarshalJSON +} + +func outputNameAndDescriptionComment(name, description string, w io.Writer) { + if strings.Index(description, "\n") == -1 { + fmt.Fprintf(w, "// %s %s\n", name, description) + return + } + + dl := strings.Split(description, "\n") + fmt.Fprintf(w, "// %s %s\n", name, strings.Join(dl, "\n// ")) +} + +func outputFieldDescriptionComment(description string, w io.Writer) { + if strings.Index(description, "\n") == -1 { + fmt.Fprintf(w, "\n // %s\n", description) + return + } + + dl := strings.Split(description, "\n") + fmt.Fprintf(w, "\n // %s\n", strings.Join(dl, "\n // ")) +} + +func cleanPackageName(pkg string) string { + pkg = strings.Replace(pkg, ".", "", -1) + pkg = strings.Replace(pkg, "_", "", -1) + pkg = strings.Replace(pkg, "-", "", -1) + return pkg +} diff --git a/output_test.go b/output_test.go new file mode 100644 index 0000000..cdf7508 --- /dev/null +++ b/output_test.go @@ -0,0 +1,91 @@ +package generate + +import ( + "reflect" + "strings" + "testing" +) + +func TestThatFieldNamesAreOrdered(t *testing.T) { + m := map[string]Field{ + "z": {}, + "b": {}, + } + + actual := getOrderedFieldNames(m) + expected := []string{"b", "z"} + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("expected %s and actual %s should match in order", strings.Join(expected, ", "), strings.Join(actual, ",")) + } +} + +func TestThatStructNamesAreOrdered(t *testing.T) { + m := map[string]Struct{ + "c": {}, + "b": {}, + "a": {}, + } + + actual := getOrderedStructNames(m) + expected := []string{"a", "b", "c"} + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("expected %s and actual %s should match in order", strings.Join(expected, ", "), strings.Join(actual, ",")) + } +} + +func TestLineAndCharacterFromOffset(t *testing.T) { + tests := []struct { + In []byte + Offset int + ExpectedLine int + ExpectedCharacter int + ExpectedError bool + }{ + { + In: []byte("Line 1\nLine 2"), + Offset: 6, + ExpectedLine: 2, + ExpectedCharacter: 1, + }, + { + In: []byte("Line 1\r\nLine 2"), + Offset: 7, + ExpectedLine: 2, + ExpectedCharacter: 1, + }, + { + In: []byte("Line 1\nLine 2"), + Offset: 0, + ExpectedLine: 1, + ExpectedCharacter: 1, + }, + { + In: []byte("Line 1\nLine 2"), + Offset: 200, + ExpectedLine: 0, + ExpectedCharacter: 0, + ExpectedError: true, + }, + { + In: []byte("Line 1\nLine 2"), + Offset: -1, + ExpectedLine: 0, + ExpectedCharacter: 0, + ExpectedError: true, + }, + } + + for _, test := range tests { + actualLine, actualCharacter, err := lineAndCharacter(test.In, test.Offset) + if err != nil && !test.ExpectedError { + t.Errorf("Unexpected error for input %s at offset %d: %v", test.In, test.Offset, err) + continue + } + + if actualLine != test.ExpectedLine || actualCharacter != test.ExpectedCharacter { + t.Errorf("For '%s' at offset %d, expected %d:%d, but got %d:%d", test.In, test.Offset, test.ExpectedLine, test.ExpectedCharacter, actualLine, actualCharacter) + } + } +} diff --git a/refresolver.go b/refresolver.go new file mode 100644 index 0000000..439c8c2 --- /dev/null +++ b/refresolver.go @@ -0,0 +1,177 @@ +package generate + +import ( + "errors" + "fmt" + "net/url" + "strings" +) + +type RefResolver struct { + schemas []*Schema + // k=uri v=Schema + pathToSchema map[string]*Schema +} + +func NewRefResolver(schemas []*Schema) *RefResolver { + return &RefResolver{ + schemas: schemas, + } +} + +func (r *RefResolver) Init() error { + r.pathToSchema = make(map[string]*Schema) + + for _, v := range r.schemas { + if err := r.mapPaths(v); err != nil { + return err + } + } + + dump := false + if dump { + for p, v := range r.pathToSchema { + fmt.Println(p, v.TypeValue) + } + } + + return nil +} + +// recusively generate path to schema +func getPath(schema *Schema, path string) string { + path = schema.PathElement + "/" + path + if schema.IsRoot() { + return path + } else { + return getPath(schema.Parent, path) + } +} + +// generate a path to given schema +func (r *RefResolver) GetPath(schema *Schema) string { + if schema.IsRoot() { + return "#" + } else { + return getPath(schema.Parent, schema.PathElement) + } +} + +func (r *RefResolver) GetSchemaByReference(schema *Schema) (*Schema, error) { + docId := schema.GetRoot().ID() + if url, err := url.Parse(docId); err != nil { + return nil, err + } else { + if ref, err := url.Parse(schema.Reference); err != nil { + return nil, err + } else { + resolvedPath := url.ResolveReference(ref) + str := resolvedPath.String() + + if path, ok := r.pathToSchema[str]; !ok { + return nil, errors.New("refresolver.GetSchemaByReference: reference not found: " + schema.Reference) + } else { + return path, nil + } + } + } +} + +func (r *RefResolver) mapPaths(schema *Schema) error { + rootURI := &url.URL{} + id := schema.ID() + if id == "" { + if err := r.InsertURI("#", schema); err != nil { + return err + } + } else { + var err error + rootURI, err = url.Parse(id) + if err != nil { + return err + } + // ensure no fragment. + rootURI.Fragment = "" + if err := r.InsertURI(rootURI.String(), schema); err != nil { + return err + } + // add as JSON pointer (?) + if err := r.InsertURI(rootURI.String()+"#", schema); err != nil { + return err + } + } + r.updateURIs(schema, *rootURI, false, false) + return nil +} + +// create a map of base URIs +func (r *RefResolver) updateURIs(schema *Schema, baseURI url.URL, checkCurrentId bool, ignoreFragments bool) error { + // already done for root, and if schema sets a new base URI + if checkCurrentId { + id := schema.ID() + if id != "" { + if newBase, err := url.Parse(id); err != nil { + return err + } else { + // if it's a JSON fragment and we're coming from part of the tree where the baseURI has changed, we need to + // ignore the fragment, since it won't be resolvable under the current baseURI. + if !(strings.HasPrefix(id, "#") && ignoreFragments) { + // map all the subschema under the new base + resolved := baseURI.ResolveReference(newBase) + if err := r.InsertURI(resolved.String(), schema); err != nil { + return err + } + if resolved.Fragment == "" { + if err := r.InsertURI(resolved.String()+"#", schema); err != nil { + return err + } + } + if err := r.updateURIs(schema, *resolved, false, false); err != nil { + return err + } + // and continue to map all subschema under the old base (except for fragments) + ignoreFragments = true + } + } + } + } + for k, subSchema := range schema.Definitions { + newBaseURI := baseURI + newBaseURI.Fragment += "/definitions/" + k + if err := r.InsertURI(newBaseURI.String(), subSchema); err != nil { + return err + } + r.updateURIs(subSchema, newBaseURI, true, ignoreFragments) + } + for k, subSchema := range schema.Properties { + newBaseURI := baseURI + newBaseURI.Fragment += "/properties/" + k + if err := r.InsertURI(newBaseURI.String(), subSchema); err != nil { + return err + } + r.updateURIs(subSchema, newBaseURI, true, ignoreFragments) + } + if schema.AdditionalProperties != nil { + newBaseURI := baseURI + newBaseURI.Fragment += "/additionalProperties" + r.updateURIs((*Schema)(schema.AdditionalProperties), newBaseURI, true, ignoreFragments) + } + if schema.Items != nil { + newBaseURI := baseURI + newBaseURI.Fragment += "/items" + r.updateURIs(schema.Items, newBaseURI, true, ignoreFragments) + } + return nil +} + +func (r *RefResolver) InsertURI(uri string, schema *Schema) error { + docId := schema.GetRoot().ID() + + if _, ok := r.pathToSchema[uri]; ok { + return errors.New("attempted to add duplicate uri: " + docId + "/" + uri) + } else { + r.pathToSchema[uri] = schema + } + + return nil +} diff --git a/test/abandoned.json b/test/abandoned.json index 9c3e022..dc33fba 100644 --- a/test/abandoned.json +++ b/test/abandoned.json @@ -13,6 +13,7 @@ }, "abandoned": { "type": "object", + "title": "Package List", "description": "List of packages marked as abandoned for this repository, the mark can be boolean or a package name/URL pointing to a recommended alternative." } } diff --git a/test/abandoned_test.go b/test/abandoned_test.go new file mode 100644 index 0000000..6fdac4d --- /dev/null +++ b/test/abandoned_test.go @@ -0,0 +1,18 @@ +package test + +import ( + "testing" + "github.com/a-h/generate/test/abandoned_gen" +) + +func TestAbandoned(t *testing.T) { + // this just tests the name generation works correctly + r := abandoned.Root{ + Name: "jonson", + Abandoned: &abandoned.PackageList{}, + } + // the test is the presence of the Abandoned field + if r.Abandoned == nil { + t.Fatal("thats the test") + } +} diff --git a/test/additionalProperties.json b/test/additionalProperties.json new file mode 100644 index 0000000..f26945b --- /dev/null +++ b/test/additionalProperties.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "additional properties", + "type": "object", + "properties": { + "property1": { + "type": "string" + }, + "property2": { + "$ref": "#/definitions/address" + }, + "property3": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "property4": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "property5": { + "type": "object", + "additionalProperties": { + "type": "object", + "title": "not so anonymous", + "properties": { + "subproperty1": { + "type": "integer" + } + } + } + }, + "property6": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "subproperty1": { + "type": "integer" + } + } + } + }, + "property7": { + "type": "object", + "additionalProperties": { + "type": "object", + "title": "hairy", + "additionalProperties": { + "type": "object", + "properties": { + "subproperty1": { + "type": "integer" + } + } + } + } + } + }, + "definitions": { + "address": { + "type": "object", + "properties": { + "city": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/test/additionalProperties2.json b/test/additionalProperties2.json new file mode 100644 index 0000000..8f6e57a --- /dev/null +++ b/test/additionalProperties2.json @@ -0,0 +1,113 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "additional properties", + "type": "object", + "properties": { + "property1": { + "type": "string" + }, + "property2": { + "$ref": "#/definitions/address" + }, + "property3": { + "type": "object", + "properties": { + "age": { "type": "integer"} + }, + "additionalProperties": { + "type": "integer" + } + }, + "property4": { + "type": "object", + "properties": { + "age": { "type": "integer"} + }, + "additionalProperties": { + "type": "string" + } + }, + "property5": { + "type": "object", + "properties": { + "age": { "type": "integer"} + }, + "additionalProperties": { + "type": "object", + "title": "not so anonymous", + "properties": { + "subproperty1": { + "type": "integer" + } + + } + } + }, + "property6": { + "type": "object", + "properties": { + "age": { "type": "integer" }, + "pronoun": { "type": "string" } + }, + "additionalProperties": { + "type": "object", + "properties": { + "subproperty1": { + "type": "integer" + } + } + } + }, + "property7": { + "type": "object", + "properties": { + "street_number": { "type": "integer"}, + "street_name": { "type": "string"}, + "po_box": { + "type": "object", + "properties": { + "suburb": { + "type": "string" + } + } + } + }, + "additionalProperties": { + "type": "object", + "title": "hairy", + "additionalProperties": { + "type": "object", + "properties": { + "color": { + "type": "string" + }, + "density": { + "type": "number" + }, + "conditions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "definitions": { + "address": { + "type": "object", + "properties": { + "city": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/test/additionalProperties2_test.go b/test/additionalProperties2_test.go new file mode 100644 index 0000000..36c8f8c --- /dev/null +++ b/test/additionalProperties2_test.go @@ -0,0 +1,107 @@ +package test + +import ( + "encoding/json" + "github.com/a-h/generate/test/additionalProperties2_gen" + "log" + "reflect" + "testing" +) + +func TestMarshalUnmarshal(t *testing.T) { + params := []struct { + Name string + Strct additionalProperties2.AdditionalProperties + Validation func(t *testing.T, prop *additionalProperties2.AdditionalProperties) + }{ + { + Name: "Base Object", + Strct: additionalProperties2.AdditionalProperties{ + Property1: "test", + }, + Validation: func(t *testing.T, prop *additionalProperties2.AdditionalProperties) { + if prop.Property1 != "test" { + t.Fatal("property1 != test") + } + }, + }, + { + Name: "Property7", + Strct: additionalProperties2.AdditionalProperties{ + Property7: &additionalProperties2.Property7{ + StreetNumber: 69, + StreetName: "Elm St", + PoBox: &additionalProperties2.PoBox{ + Suburb: "Smallville", + }, + AdditionalProperties: map[string]map[string]*additionalProperties2.Anonymous1{ + "red": { + "blue": { + Color: "green", + Conditions: []*additionalProperties2.ConditionsItems{ + {Name: "dry"}, + }, + Density: 42.42, + }, + }, + "orange": {}, + }, + }, + }, + Validation: func(t *testing.T, prop *additionalProperties2.AdditionalProperties) { + + if prop.Property7.StreetNumber != 69 { + t.Fatal("wrong value") + } + + if len(prop.Property7.AdditionalProperties) != 2 { + t.Fatal("not enough additionalProperties") + } + + if prop.Property7.AdditionalProperties["red"]["blue"].Color != "green" { + t.Fatal("wrong nested value") + } + + if prop.Property7.AdditionalProperties["red"]["blue"].Density != 42.42 { + t.Fatal("wrong nested value") + } + }, + }, + } + + for _, p := range params { + if str, err := json.MarshalIndent(&p.Strct, "", " "); err != nil { + t.Fatal(err) + } else { + //log.Println(string(str)) + strct2 := &additionalProperties2.AdditionalProperties{} + if err := json.Unmarshal(str, &strct2); err != nil { + t.Fatal(err) + } + + if reflect.DeepEqual(p.Strct, strct2) { + log.Fatal("unmarshaled struct != given struct") + } + + p.Validation(t, strct2) + + if str, err := json.MarshalIndent(&strct2, "", " "); err != nil { + t.Fatal(err) + } else { + //log.Println(string(str)) + strct3 := &additionalProperties2.AdditionalProperties{} + if err := json.Unmarshal(str, &strct3); err != nil { + t.Fatal(err) + } + + if reflect.DeepEqual(p.Strct, strct3) { + log.Fatal("unmarshaled struct != given struct") + } + + p.Validation(t, strct3) + + } + + } + } +} diff --git a/test/additionalPropertiesMarshal.json b/test/additionalPropertiesMarshal.json new file mode 100644 index 0000000..8b7d3ec --- /dev/null +++ b/test/additionalPropertiesMarshal.json @@ -0,0 +1,89 @@ +{ + "definitions": { + "thing": { + "type": "object" + }, + + "apRefNoProp": { + "additionalProperties": { + "$ref": "#/definitions/thing" + }, + "type": "object" + }, + "apRefProp": { + "properties": { + "stuff": { + "type": "string" + } + }, + "additionalProperties": { + "$ref": "#/definitions/thing" + }, + "type": "object" + }, + "apRefReqProp": { + "properties": { + "stuff": { + "type": "string" + } + }, + "additionalProperties": { + "$ref": "#/definitions/thing" + }, + "required": [ "stuff" ], + "type": "object" + }, + + + "apTrueNoProp": { + "additionalProperties": true, + "type": "object" + }, + "apFalseNoProp": { + "additionalProperties": false, + "type": "object" + }, + + + "apTrueProp": { + "properties": { + "stuff": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "apFalseProp": { + "properties": { + "stuff": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + + + "apTrueReqProp": { + "properties": { + "stuff": { + "type": "string" + } + }, + "additionalProperties": true, + "required": [ "stuff" ], + "type": "object" + }, + "apFalseReqProp": { + "properties": { + "stuff": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ "stuff" ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/test/additionalPropertiesMarshal_test.go b/test/additionalPropertiesMarshal_test.go new file mode 100644 index 0000000..4f2a047 --- /dev/null +++ b/test/additionalPropertiesMarshal_test.go @@ -0,0 +1,272 @@ +package test + +import ( + "testing" + gen "github.com/a-h/generate/test/additionalPropertiesMarshal_gen" + "encoding/json" + "reflect" +) + +func TestApRefNoProp(t *testing.T) { +} + +func TestApRefProp(t *testing.T) { +} + +func TestApRefReqProp(t *testing.T) { +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +func TestApTrueNoProp(t *testing.T) { + + noPropData := `{"a": "b", "c": 42 }` + + ap := gen.ApTrueNoProp{} + err := json.Unmarshal([]byte(noPropData), &ap) + if err != nil { + t.Fatal(err) + } + + if len(ap.AdditionalProperties) != 2 { + t.Fatalf("Wrong number of additionalProperties: %d", len(ap.AdditionalProperties)) + } + + if s, ok := ap.AdditionalProperties["a"].(string); !ok { + t.Fatalf("a was not a string") + } else { + if s != "b" { + t.Fatalf("wrong value for a: \"%s\" (should be \"b\")", s) + } + } + + typ := reflect.TypeOf(ap.AdditionalProperties["c"]) + + if c, ok := ap.AdditionalProperties["c"].(float64); !ok { + t.Fatalf("c was not an number (it was a %s)", typ.Name()) + } else { + if c != 42 { + t.Fatalf("wrong value for c: \"%f\" (should be \"42\")", c) + } + } +} + + +func TestApTrueProp(t *testing.T) { + data := `{"a": "b", "c": 42, "stuff": "xyz" }` + + ap := gen.ApTrueProp{} + err := json.Unmarshal([]byte(data), &ap) + if err != nil { + t.Fatal(err) + } + + if len(ap.AdditionalProperties) != 2 { + t.Fatalf("Wrong number of additionalProperties: %d", len(ap.AdditionalProperties)) + } + + if s, ok := ap.AdditionalProperties["a"].(string); !ok { + t.Fatalf("a was not a string") + } else { + if s != "b" { + t.Fatalf("wrong value for a: \"%s\" (should be \"b\")", s) + } + } + + typ := reflect.TypeOf(ap.AdditionalProperties["c"]) + + if c, ok := ap.AdditionalProperties["c"].(float64); !ok { + t.Fatalf("c was not an number (it was a %s)", typ.Name()) + } else { + if c != 42 { + t.Fatalf("wrong value for c: \"%f\" (should be \"42\")", c) + } + } + + if ap.Stuff != "xyz" { + t.Fatalf("invalid stuff value: \"%s\"", ap.Stuff) + } +} + +func TestApTrueReqProp(t *testing.T) { + dataGood := `{"a": "b", "c": 42, "stuff": "xyz" }` + dataBad := `{"a": "b", "c": 42 }` + + { + ap := gen.ApTrueReqProp{} + err := json.Unmarshal([]byte(dataBad), &ap) + if err == nil { + t.Fatalf("should have returned an error, required field missing") + } + } + + ap := gen.ApTrueReqProp{} + err := json.Unmarshal([]byte(dataGood), &ap) + if err != nil { + t.Fatal(err) + } + + if len(ap.AdditionalProperties) != 2 { + t.Fatalf("Wrong number of additionalProperties: %d", len(ap.AdditionalProperties)) + } + + if s, ok := ap.AdditionalProperties["a"].(string); !ok { + t.Fatalf("a was not a string") + } else { + if s != "b" { + t.Fatalf("wrong value for a: \"%s\" (should be \"b\")", s) + } + } + + typ := reflect.TypeOf(ap.AdditionalProperties["c"]) + + if c, ok := ap.AdditionalProperties["c"].(float64); !ok { + t.Fatalf("c was not an number (it was a %s)", typ.Name()) + } else { + if c != 42 { + t.Fatalf("wrong value for c: \"%f\" (should be \"42\")", c) + } + } + + if ap.Stuff != "xyz" { + t.Fatalf("invalid stuff value: \"%s\"", ap.Stuff) + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +func TestApFalseNoProp(t *testing.T) { + + dataBad1 := `{"a": "b", "c": 42, "stuff": "xyz"}` + dataBad2 := `{"a": "b", "c": 42}` + dataBad3 := `{"stuff": "xyz"}` + dataGood1 := `{}` + + { + ap := gen.ApFalseNoProp{} + err := json.Unmarshal([]byte(dataBad1), &ap) + if err == nil { + t.Fatalf("should have returned an error, required field missing") + } + } + + { + ap := gen.ApFalseNoProp{} + err := json.Unmarshal([]byte(dataBad2), &ap) + if err == nil { + t.Fatalf("should have returned an error, required field missing") + } + } + + { + ap := gen.ApFalseNoProp{} + err := json.Unmarshal([]byte(dataBad3), &ap) + if err == nil { + t.Fatalf("should have returned an error, required field missing") + } + } + + { + ap := gen.ApFalseNoProp{} + err := json.Unmarshal([]byte(dataGood1), &ap) + if err != nil { + t.Fatal(err) + } + } +} + + +func TestApFalseProp(t *testing.T) { + dataBad1 := `{"a": "b", "c": 42, "stuff": "xyz"}` + dataBad2 := `{"a": "b", "c": 42}` + dataGood1 := `{"stuff": "xyz"}` + dataGood2 := `{}` + + { + ap := gen.ApFalseProp{} + err := json.Unmarshal([]byte(dataBad1), &ap) + if err == nil { + t.Fatalf("should have returned an error, required field missing") + } + } + + { + ap := gen.ApFalseProp{} + err := json.Unmarshal([]byte(dataBad2), &ap) + if err == nil { + t.Fatalf("should have returned an error, required field missing") + } + } + + { + ap := gen.ApFalseProp{} + err := json.Unmarshal([]byte(dataGood1), &ap) + if err != nil { + t.Fatal(err) + } + if ap.Stuff != "xyz" { + t.Fatalf("invalid stuff value: \"%s\"", ap.Stuff) + } + } + + { + ap := gen.ApFalseProp{} + err := json.Unmarshal([]byte(dataGood2), &ap) + if err != nil { + t.Fatal(err) + } + if ap.Stuff != "" { + t.Fatalf("invalid stuff value: \"%s\"", ap.Stuff) + } + } + + ap := gen.ApFalseProp{} + if _, ok := reflect.TypeOf(ap).FieldByName("AdditionalProperties"); ok { + t.Fatalf("AdditionalProperties was generated where it should not have been") + } +} + +func TestApFalseReqProp(t *testing.T) { + dataBad1 := `{"a": "b", "c": 42, "stuff": "xyz"}` + dataBad2 := `{"a": "b", "c": 42}` + dataBad3 := `{}` + dataGood := `{"stuff": "xyz"}` + + { + ap := gen.ApFalseReqProp{} + err := json.Unmarshal([]byte(dataBad1), &ap) + if err == nil { + t.Fatalf("should have returned an error, required field missing") + } + } + + { + ap := gen.ApFalseReqProp{} + err := json.Unmarshal([]byte(dataBad2), &ap) + if err == nil { + t.Fatalf("should have returned an error, required field missing") + } + } + + { + ap := gen.ApFalseReqProp{} + err := json.Unmarshal([]byte(dataBad3), &ap) + if err == nil { + t.Fatalf("should have returned an error, required field missing") + } + } + + ap := gen.ApFalseReqProp{} + err := json.Unmarshal([]byte(dataGood), &ap) + if err != nil { + t.Fatal(err) + } + + if _, ok := reflect.TypeOf(ap).FieldByName("AdditionalProperties"); ok { + t.Fatalf("AdditionalProperties was generated where it should not have been") + } + + if ap.Stuff != "xyz" { + t.Fatalf("invalid stuff value: \"%s\"", ap.Stuff) + } +} \ No newline at end of file diff --git a/test/anonarrayitems.json b/test/anonarrayitems.json new file mode 100644 index 0000000..5725883 --- /dev/null +++ b/test/anonarrayitems.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AnonArray", + "type": "object", + "properties": { + "arr": { + "$ref": "#/definitions/TheArray" + } + }, + "definitions": { + "TheArray": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "anon": { + "type": "array", + "items": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + } + } + } + } + + } +} \ No newline at end of file diff --git a/test/aprefnoprop.json b/test/aprefnoprop.json new file mode 100644 index 0000000..d748e35 --- /dev/null +++ b/test/aprefnoprop.json @@ -0,0 +1,13 @@ +{ + "definitions": { + "apRefNoProp": { + "additionalProperties": { + "$ref": "#/definitions/thing" + }, + "type": "object" + }, + "thing": { + "type": "object" + } + } +} \ No newline at end of file diff --git a/test/array.json b/test/array.json new file mode 100644 index 0000000..9817891 --- /dev/null +++ b/test/array.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "ProductSet", + "type": "object", + "properties": { + "the array": { + "type": "array", + "items": { + "type": "integer" + } + } + } +} \ No newline at end of file diff --git a/test/arrayofref.json b/test/arrayofref.json new file mode 100644 index 0000000..1dd85ed --- /dev/null +++ b/test/arrayofref.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "ProductSet", + "type": "array", + "items": { + "$ref": "#/definitions/Product" + }, + "definitions": { + "Product": { + "title": "Product", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a product", + "type": "number" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "required": ["id", "name", "price"] + } + } +} \ No newline at end of file diff --git a/test/customer.json b/test/customer.json new file mode 100644 index 0000000..ac2db67 --- /dev/null +++ b/test/customer.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://thing", + + "definitions": { + "address": { + "type": "object", + "properties": { + "street_address": { "type": "string" }, + "city": { "type": "string" }, + "state": { "type": "string" } + }, + "required": ["street_address", "city", "state"] + } + }, + + "type": "object", + + "properties": { + "billing_address": { "$ref": "#/definitions/address" }, + "shipping_address": { "$ref": "#/definitions/address" } + } +} \ No newline at end of file diff --git a/test/example1_test.go b/test/example1_test.go new file mode 100644 index 0000000..78e8563 --- /dev/null +++ b/test/example1_test.go @@ -0,0 +1,47 @@ +package test + +import ( + "encoding/json" + "testing" + "github.com/a-h/generate/test/example1_gen" +) + +func TestExample1(t *testing.T) { + params := []struct { + Name string + Data string + ExpectedResult bool + }{ + { + Name: "Blue Sky", + Data: `{ + "id": 1, + "name": "Unbridled Optimism 2.0", + "price": 99.99, + "tags": [ "happy" ] }`, + ExpectedResult: true, + }, + { + Name: "Missing Price", + Data: `{ + "id": 1, + "name": "Unbridled Optimism 2.0", + "tags": [ "happy" ] }`, + ExpectedResult: false, + }, + } + + for _, param := range params { + + prod := &example1.Product{} + if err := json.Unmarshal([]byte(param.Data), &prod); err != nil { + if param.ExpectedResult { + t.Fatal(err) + } + } else { + if !param.ExpectedResult { + t.Fatal("Expected failure, got success: " + param.Name) + } + } + } +} diff --git a/test/example1a.json b/test/example1a.json index 6dd2108..1ef19a2 100644 --- a/test/example1a.json +++ b/test/example1a.json @@ -44,10 +44,6 @@ "width", "height" ] - }, - "warehouseLocation": { - "description": "Coordinates of the warehouse with the product", - "$ref": "http://json-schema.org/geo" } }, "required": [ diff --git a/test/issue14.json b/test/issue14.json index 90087e5..123018a 100644 --- a/test/issue14.json +++ b/test/issue14.json @@ -10,6 +10,7 @@ "name": { "type": "string", "description": "Repository name." + }, "repositories": { "type": "array", diff --git a/test/issue39.json b/test/issue39.json new file mode 100644 index 0000000..df9c870 --- /dev/null +++ b/test/issue39.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "House", + "type": "object", + "definitions": { + "address": { + "type": "object", + "properties": { + "number": { "type": "integer" }, + "postcode": { "type": "string" } + } + }, + "owners": { + "type": "array", + "items": { + "type": "object", + "title": "person", + "properties": { + "name": { "type": "string" } + } + } + } + }, + "properties": { + "address": { "$ref": "#/definitions/address" }, + "owners": { "$ref": "#/definitions/owners" } + } +} \ No newline at end of file diff --git a/test/issue6.json b/test/issue6.json index 9f3fbf6..537c17c 100644 --- a/test/issue6.json +++ b/test/issue6.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "physical_server.json", + "$id": "file://physical_server.json", "description": "Physical Server asset", "type": "object", "properties": { diff --git a/test/multiple.json b/test/multiple.json new file mode 100644 index 0000000..ce214d3 --- /dev/null +++ b/test/multiple.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Multipass", + "type": [ + "array", + "object" + ], + "items": { + "type": "string" + }, + "properties": { + "arr": { + "$ref": "#/definitions/Thing" + } + }, + "definitions": { + "Thing": { + "type": [ + "array", + "object" + ], + "items": { + "type": "integer" + }, + "properties": { + "age": { + "type": ["integer", "string"] + } + } + } + } +} diff --git a/test/nestedarrayofref.json b/test/nestedarrayofref.json new file mode 100644 index 0000000..aeae354 --- /dev/null +++ b/test/nestedarrayofref.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AccountTags", + "comment": "goal: AccountTags [][]*Tags", + "type": "array", + "items": { + "$ref": "#/definitions/Accounts" + }, + "definitions": { + "Accounts": { + "title": "Accounts", + "type": "array", + "items": { + "$ref": "#/definitions/Tags" + } + }, + "Tags": { + "title": "Tags", + "type": "object", + "properties": { + "tag": { + "type": "string" + }, + "things": { + "type": "array", + "items": { + "$ref": "#/definitions/Thing" + } + }, + "mainThing": { + "$ref": "#/definitions/Thing" + } + } + }, + "Thing": { + "type": "object", + "properties": { + "foo": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer" + } + } + }, + "bar": { + "type": "array", + "items": { + "type": "integer" + } + }, + "baz": { + "type": "array", + "items": { + "type": "object", + "properties": { + "qux": { + "type": "string" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/test/recursion.json b/test/recursion.json new file mode 100644 index 0000000..552518f --- /dev/null +++ b/test/recursion.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + + "definitions": { + "person": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "children": { + "type": "array", + "items": { "$ref": "#/definitions/person" }, + "default": [] + } + } + } + }, + + "type": "object", + + "properties": { + "person": { "$ref": "#/definitions/person" } + } +} \ No newline at end of file diff --git a/test/schemaid.json b/test/schemaid.json new file mode 100644 index 0000000..751d2a0 --- /dev/null +++ b/test/schemaid.json @@ -0,0 +1,16 @@ +{ + "$id": "http://example.com/root.json", + "definitions": { + "A": { "$id": "#foo" }, + "B": { + "$id": "other.json", + "definitions": { + "X": { "$id": "#bar" }, + "Y": { "$id": "t/inner.json" } + } + }, + "C": { + "$id": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f" + } + } +} diff --git a/test/simple.json b/test/simple.json new file mode 100644 index 0000000..93fb10f --- /dev/null +++ b/test/simple.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Simple", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "subbie": { + "type": "object", + "title": "Not So Anonymous", + "properties": { + "age": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/test/test.json b/test/test.json index bc15c1d..801d21b 100644 --- a/test/test.json +++ b/test/test.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Example", - "$id": "Example", + "$id": "http://Example", "type": "object", "description": "example", "definitions": { diff --git a/test/vega-lite-v2.0.json_ b/test/vega-lite-v2.0.json_ new file mode 100644 index 0000000..1a24cfa --- /dev/null +++ b/test/vega-lite-v2.0.json_ @@ -0,0 +1,6257 @@ +{ + "$ref": "#/definitions/TopLevelExtendedSpec", + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "Aggregate": { + "$ref": "#/definitions/AggregateOp" + }, + "AggregateOp": { + "enum": [ + "argmax", + "argmin", + "average", + "count", + "distinct", + "max", + "mean", + "median", + "min", + "missing", + "q1", + "q3", + "ci0", + "ci1", + "stdev", + "stdevp", + "sum", + "valid", + "values", + "variance", + "variancep" + ], + "type": "string" + }, + "AggregateTransform": { + "additionalProperties": false, + "properties": { + "aggregate": { + "description": "Array of objects that define fields to aggregate.", + "items": { + "$ref": "#/definitions/AggregatedFieldDef" + }, + "type": "array" + }, + "groupby": { + "description": "The data fields to group by. If not specified, a single group containing all data objects will be used.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "aggregate" + ], + "type": "object" + }, + "AggregatedFieldDef": { + "additionalProperties": false, + "properties": { + "as": { + "description": "The output field names to use for each aggregated field.", + "type": "string" + }, + "field": { + "description": "The data field for which to compute aggregate function.", + "type": "string" + }, + "op": { + "$ref": "#/definitions/AggregateOp", + "description": "The aggregation operations to apply to the fields, such as sum, average or count.\nSee the [full list of supported aggregation operations](https://vega.github.io/vega-lite/docs/aggregate.html#ops)\nfor more information." + } + }, + "required": [ + "op", + "field", + "as" + ], + "type": "object" + }, + "Anchor": { + "enum": [ + "start", + "middle", + "end" + ], + "type": "string" + }, + "AnyMark": { + "anyOf": [ + { + "$ref": "#/definitions/Mark" + }, + { + "$ref": "#/definitions/MarkDef" + } + ] + }, + "AutoSizeParams": { + "additionalProperties": false, + "properties": { + "contains": { + "description": "Determines how size calculation should be performed, one of `\"content\"` or `\"padding\"`. The default setting (`\"content\"`) inteprets the width and height settings as the data rectangle (plotting) dimensions, to which padding is then added. In contrast, the `\"padding\"` setting includes the padding within the view size calculations, such that the width and height settings indicate the **total** intended size of the view.\n\n__Default value__: `\"content\"`", + "enum": [ + "content", + "padding" + ], + "type": "string" + }, + "resize": { + "description": "A boolean flag indicating if autosize layout should be re-calculated on every view update.\n\n__Default value__: `false`", + "type": "boolean" + }, + "type": { + "$ref": "#/definitions/AutosizeType", + "description": "The sizing format type. One of `\"pad\"`, `\"fit\"` or `\"none\"`. See the [autosize type](https://vega.github.io/vega-lite/docs/size.html#autosize) documentation for descriptions of each.\n\n__Default value__: `\"pad\"`" + } + }, + "type": "object" + }, + "AutosizeType": { + "enum": [ + "pad", + "fit", + "none" + ], + "type": "string" + }, + "Axis": { + "additionalProperties": false, + "properties": { + "domain": { + "description": "A boolean flag indicating if the domain (the axis baseline) should be included as part of the axis.\n\n__Default value:__ `true`", + "type": "boolean" + }, + "format": { + "description": "The formatting pattern for labels. This is D3's [number format pattern](https://github.com/d3/d3-format#locale_format) for quantitative fields and D3's [time format pattern](https://github.com/d3/d3-time-format#locale_format) for time field.\n\nSee the [format documentation](format.html) for more information.\n\n__Default value:__ derived from [numberFormat](config.html#format) config for quantitative fields and from [timeFormat](config.html#format) config for temporal fields.", + "type": "string" + }, + "grid": { + "description": "A boolean flag indicating if grid lines should be included as part of the axis\n\n__Default value:__ `true` for [continuous scales](scale.html#continuous) that are not binned; otherwise, `false`.", + "type": "boolean" + }, + "labelAngle": { + "description": "The rotation angle of the axis labels.\n\n__Default value:__ `-90` for nominal and ordinal fields; `0` otherwise.", + "maximum": 360, + "minimum": -360, + "type": "number" + }, + "labelBound": { + "description": "Indicates if labels should be hidden if they exceed the axis range. If `false `(the default) no bounds overlap analysis is performed. If `true`, labels will be hidden if they exceed the axis range by more than 1 pixel. If this property is a number, it specifies the pixel tolerance: the maximum amount by which a label bounding box may exceed the axis range.\n\n__Default value:__ `false`.", + "type": [ + "boolean", + "number" + ] + }, + "labelFlush": { + "description": "Indicates if the first and last axis labels should be aligned flush with the scale range. Flush alignment for a horizontal axis will left-align the first label and right-align the last label. For vertical axes, bottom and top text baselines are applied instead. If this property is a number, it also indicates the number of pixels by which to offset the first and last labels; for example, a value of 2 will flush-align the first and last labels and also push them 2 pixels outward from the center of the axis. The additional adjustment can sometimes help the labels better visually group with corresponding axis ticks.\n\n__Default value:__ `true` for axis of a continuous x-scale. Otherwise, `false`.", + "type": [ + "boolean", + "number" + ] + }, + "labelOverlap": { + "anyOf": [ + { + "type": "boolean" + }, + { + "enum": [ + "parity" + ], + "type": "string" + }, + { + "enum": [ + "greedy" + ], + "type": "string" + } + ], + "description": "The strategy to use for resolving overlap of axis labels. If `false` (the default), no overlap reduction is attempted. If set to `true` or `\"parity\"`, a strategy of removing every other label is used (this works well for standard linear axes). If set to `\"greedy\"`, a linear scan of the labels is performed, removing any labels that overlaps with the last visible label (this often works better for log-scaled axes).\n\n__Default value:__ `true` for non-nominal fields with non-log scales; `\"greedy\"` for log scales; otherwise `false`." + }, + "labelPadding": { + "description": "The padding, in pixels, between axis and text labels.", + "type": "number" + }, + "labels": { + "description": "A boolean flag indicating if labels should be included as part of the axis.\n\n__Default value:__ `true`.", + "type": "boolean" + }, + "maxExtent": { + "description": "The maximum extent in pixels that axis ticks and labels should use. This determines a maximum offset value for axis titles.\n\n__Default value:__ `undefined`.", + "type": "number" + }, + "minExtent": { + "description": "The minimum extent in pixels that axis ticks and labels should use. This determines a minimum offset value for axis titles.\n\n__Default value:__ `30` for y-axis; `undefined` for x-axis.", + "type": "number" + }, + "offset": { + "description": "The offset, in pixels, by which to displace the axis from the edge of the enclosing group or data rectangle.\n\n__Default value:__ derived from the [axis config](config.html#facet-scale-config)'s `offset` (`0` by default)", + "type": "number" + }, + "orient": { + "$ref": "#/definitions/AxisOrient", + "description": "The orientation of the axis. One of `\"top\"`, `\"bottom\"`, `\"left\"` or `\"right\"`. The orientation can be used to further specialize the axis type (e.g., a y axis oriented for the right edge of the chart).\n\n__Default value:__ `\"bottom\"` for x-axes and `\"left\"` for y-axes." + }, + "position": { + "description": "The anchor position of the axis in pixels. For x-axis with top or bottom orientation, this sets the axis group x coordinate. For y-axis with left or right orientation, this sets the axis group y coordinate.\n\n__Default value__: `0`", + "type": "number" + }, + "tickCount": { + "description": "A desired number of ticks, for axes visualizing quantitative scales. The resulting number may be different so that values are \"nice\" (multiples of 2, 5, 10) and lie within the underlying scale's range.", + "type": "number" + }, + "tickSize": { + "description": "The size in pixels of axis ticks.", + "minimum": 0, + "type": "number" + }, + "ticks": { + "description": "Boolean value that determines whether the axis should include ticks.", + "type": "boolean" + }, + "title": { + "description": "A title for the field. If `null`, the title will be removed.\n\n__Default value:__ derived from the field's name and transformation function (`aggregate`, `bin` and `timeUnit`). If the field has an aggregate function, the function is displayed as a part of the title (e.g., `\"Sum of Profit\"`). If the field is binned or has a time unit applied, the applied function will be denoted in parentheses (e.g., `\"Profit (binned)\"`, `\"Transaction Date (year-month)\"`). Otherwise, the title is simply the field name.\n\n__Note__: You can customize the default field title format by providing the [`fieldTitle` property in the [config](config.html) or [`fieldTitle` function via the `compile` function's options](compile.html#field-title).", + "type": [ + "string", + "null" + ] + }, + "titleMaxLength": { + "description": "Max length for axis title if the title is automatically generated from the field's description.", + "type": "number" + }, + "titlePadding": { + "description": "The padding, in pixels, between title and axis.", + "type": "number" + }, + "values": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "items": { + "$ref": "#/definitions/DateTime" + }, + "type": "array" + } + ], + "description": "Explicitly set the visible axis tick values." + }, + "zindex": { + "description": "A non-positive integer indicating z-index of the axis.\nIf zindex is 0, axes should be drawn behind all chart elements.\nTo put them in front, use `\"zindex = 1\"`.\n\n__Default value:__ `1` (in front of the marks) for actual axis and `0` (behind the marks) for grids.", + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "AxisConfig": { + "additionalProperties": false, + "properties": { + "bandPosition": { + "description": "An interpolation fraction indicating where, for `band` scales, axis ticks should be positioned. A value of `0` places ticks at the left edge of their bands. A value of `0.5` places ticks in the middle of their bands.", + "type": "number" + }, + "domain": { + "description": "A boolean flag indicating if the domain (the axis baseline) should be included as part of the axis.\n\n__Default value:__ `true`", + "type": "boolean" + }, + "domainColor": { + "description": "Color of axis domain line.\n\n__Default value:__ (none, using Vega default).", + "type": "string" + }, + "domainWidth": { + "description": "Stroke width of axis domain line\n\n__Default value:__ (none, using Vega default).", + "type": "number" + }, + "grid": { + "description": "A boolean flag indicating if grid lines should be included as part of the axis\n\n__Default value:__ `true` for [continuous scales](scale.html#continuous) that are not binned; otherwise, `false`.", + "type": "boolean" + }, + "gridColor": { + "description": "Color of gridlines.", + "type": "string" + }, + "gridDash": { + "description": "The offset (in pixels) into which to begin drawing with the grid dash array.", + "items": { + "type": "number" + }, + "type": "array" + }, + "gridOpacity": { + "description": "The stroke opacity of grid (value between [0,1])\n\n__Default value:__ (`1` by default)", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "gridWidth": { + "description": "The grid width, in pixels.", + "minimum": 0, + "type": "number" + }, + "labelAngle": { + "description": "The rotation angle of the axis labels.\n\n__Default value:__ `-90` for nominal and ordinal fields; `0` otherwise.", + "maximum": 360, + "minimum": -360, + "type": "number" + }, + "labelBound": { + "description": "Indicates if labels should be hidden if they exceed the axis range. If `false `(the default) no bounds overlap analysis is performed. If `true`, labels will be hidden if they exceed the axis range by more than 1 pixel. If this property is a number, it specifies the pixel tolerance: the maximum amount by which a label bounding box may exceed the axis range.\n\n__Default value:__ `false`.", + "type": [ + "boolean", + "number" + ] + }, + "labelColor": { + "description": "The color of the tick label, can be in hex color code or regular color name.", + "type": "string" + }, + "labelFlush": { + "description": "Indicates if the first and last axis labels should be aligned flush with the scale range. Flush alignment for a horizontal axis will left-align the first label and right-align the last label. For vertical axes, bottom and top text baselines are applied instead. If this property is a number, it also indicates the number of pixels by which to offset the first and last labels; for example, a value of 2 will flush-align the first and last labels and also push them 2 pixels outward from the center of the axis. The additional adjustment can sometimes help the labels better visually group with corresponding axis ticks.\n\n__Default value:__ `true` for axis of a continuous x-scale. Otherwise, `false`.", + "type": [ + "boolean", + "number" + ] + }, + "labelFont": { + "description": "The font of the tick label.", + "type": "string" + }, + "labelFontSize": { + "description": "The font size of the label, in pixels.", + "minimum": 0, + "type": "number" + }, + "labelLimit": { + "description": "Maximum allowed pixel width of axis tick labels.", + "type": "number" + }, + "labelOverlap": { + "anyOf": [ + { + "type": "boolean" + }, + { + "enum": [ + "parity" + ], + "type": "string" + }, + { + "enum": [ + "greedy" + ], + "type": "string" + } + ], + "description": "The strategy to use for resolving overlap of axis labels. If `false` (the default), no overlap reduction is attempted. If set to `true` or `\"parity\"`, a strategy of removing every other label is used (this works well for standard linear axes). If set to `\"greedy\"`, a linear scan of the labels is performed, removing any labels that overlaps with the last visible label (this often works better for log-scaled axes).\n\n__Default value:__ `true` for non-nominal fields with non-log scales; `\"greedy\"` for log scales; otherwise `false`." + }, + "labelPadding": { + "description": "The padding, in pixels, between axis and text labels.", + "type": "number" + }, + "labels": { + "description": "A boolean flag indicating if labels should be included as part of the axis.\n\n__Default value:__ `true`.", + "type": "boolean" + }, + "maxExtent": { + "description": "The maximum extent in pixels that axis ticks and labels should use. This determines a maximum offset value for axis titles.\n\n__Default value:__ `undefined`.", + "type": "number" + }, + "minExtent": { + "description": "The minimum extent in pixels that axis ticks and labels should use. This determines a minimum offset value for axis titles.\n\n__Default value:__ `30` for y-axis; `undefined` for x-axis.", + "type": "number" + }, + "shortTimeLabels": { + "description": "Whether month names and weekday names should be abbreviated.\n\n__Default value:__ `false`", + "type": "boolean" + }, + "tickColor": { + "description": "The color of the axis's tick.", + "type": "string" + }, + "tickRound": { + "description": "Boolean flag indicating if pixel position values should be rounded to the nearest integer.", + "type": "boolean" + }, + "tickSize": { + "description": "The size in pixels of axis ticks.", + "minimum": 0, + "type": "number" + }, + "tickWidth": { + "description": "The width, in pixels, of ticks.", + "minimum": 0, + "type": "number" + }, + "ticks": { + "description": "Boolean value that determines whether the axis should include ticks.", + "type": "boolean" + }, + "titleAlign": { + "description": "Horizontal text alignment of axis titles.", + "type": "string" + }, + "titleAngle": { + "description": "Angle in degrees of axis titles.", + "type": "number" + }, + "titleBaseline": { + "description": "Vertical text baseline for axis titles.", + "type": "string" + }, + "titleColor": { + "description": "Color of the title, can be in hex color code or regular color name.", + "type": "string" + }, + "titleFont": { + "description": "Font of the title. (e.g., `\"Helvetica Neue\"`).", + "type": "string" + }, + "titleFontSize": { + "description": "Font size of the title.", + "minimum": 0, + "type": "number" + }, + "titleFontWeight": { + "description": "Font weight of the title. (e.g., `\"bold\"`).", + "type": [ + "string", + "number" + ] + }, + "titleLimit": { + "description": "Maximum allowed pixel width of axis titles.", + "type": "number" + }, + "titleMaxLength": { + "description": "Max length for axis title if the title is automatically generated from the field's description.", + "type": "number" + }, + "titlePadding": { + "description": "The padding, in pixels, between title and axis.", + "type": "number" + }, + "titleX": { + "description": "X-coordinate of the axis title relative to the axis group.", + "type": "number" + }, + "titleY": { + "description": "Y-coordinate of the axis title relative to the axis group.", + "type": "number" + } + }, + "type": "object" + }, + "AxisOrient": { + "enum": [ + "top", + "right", + "left", + "bottom" + ], + "type": "string" + }, + "AxisResolveMap": { + "additionalProperties": false, + "properties": { + "x": { + "$ref": "#/definitions/ResolveMode" + }, + "y": { + "$ref": "#/definitions/ResolveMode" + } + }, + "type": "object" + }, + "BarConfig": { + "additionalProperties": false, + "properties": { + "align": { + "$ref": "#/definitions/HorizontalAlign", + "description": "The horizontal alignment of the text. One of `\"left\"`, `\"right\"`, `\"center\"`." + }, + "angle": { + "description": "The rotation angle of the text, in degrees.", + "maximum": 360, + "minimum": 0, + "type": "number" + }, + "baseline": { + "$ref": "#/definitions/VerticalAlign", + "description": "The vertical alignment of the text. One of `\"top\"`, `\"middle\"`, `\"bottom\"`.\n\n__Default value:__ `\"middle\"`" + }, + "binSpacing": { + "description": "Offset between bar for binned field. Ideal value for this is either 0 (Preferred by statisticians) or 1 (Vega-Lite Default, D3 example style).\n\n__Default value:__ `1`", + "minimum": 0, + "type": "number" + }, + "color": { + "description": "Default color. Note that `fill` and `stroke` have higher precedence than `color` and will override `color`.\n\n__Default value:__ `\"#4682b4\"`\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "string" + }, + "continuousBandSize": { + "description": "The default size of the bars on continuous scales.\n\n__Default value:__ `5`", + "minimum": 0, + "type": "number" + }, + "discreteBandSize": { + "description": "The size of the bars. If unspecified, the default size is `bandSize-1`,\nwhich provides 1 pixel offset between bars.", + "minimum": 0, + "type": "number" + }, + "dx": { + "description": "The horizontal offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "dy": { + "description": "The vertical offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "fill": { + "description": "Default Fill Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "fillOpacity": { + "description": "The fill opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "filled": { + "description": "Whether the mark's color should be used as fill color instead of stroke color.\n\n__Default value:__ `true` for all marks except `point` and `false` for `point`.\n\n__Applicable for:__ `bar`, `point`, `circle`, `square`, and `area` marks.\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "boolean" + }, + "font": { + "description": "The typeface to set the text in (e.g., `\"Helvetica Neue\"`).", + "type": "string" + }, + "fontSize": { + "description": "The font size, in pixels.", + "minimum": 0, + "type": "number" + }, + "fontStyle": { + "$ref": "#/definitions/FontStyle", + "description": "The font style (e.g., `\"italic\"`)." + }, + "fontWeight": { + "anyOf": [ + { + "$ref": "#/definitions/FontWeight" + }, + { + "$ref": "#/definitions/FontWeightNumber" + } + ], + "description": "The font weight (e.g., `\"bold\"`)." + }, + "interpolate": { + "$ref": "#/definitions/Interpolate", + "description": "The line interpolation method to use for line and area marks. One of the following:\n- `\"linear\"`: piecewise linear segments, as in a polyline.\n- `\"linear-closed\"`: close the linear segments to form a polygon.\n- `\"step\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"step-before\"`: alternate between vertical and horizontal segments, as in a step function.\n- `\"step-after\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"basis\"`: a B-spline, with control point duplication on the ends.\n- `\"basis-open\"`: an open B-spline; may not intersect the start or end.\n- `\"basis-closed\"`: a closed B-spline, as in a loop.\n- `\"cardinal\"`: a Cardinal spline, with control point duplication on the ends.\n- `\"cardinal-open\"`: an open Cardinal spline; may not intersect the start or end, but will intersect other control points.\n- `\"cardinal-closed\"`: a closed Cardinal spline, as in a loop.\n- `\"bundle\"`: equivalent to basis, except the tension parameter is used to straighten the spline.\n- `\"monotone\"`: cubic interpolation that preserves monotonicity in y." + }, + "limit": { + "description": "The maximum length of the text mark in pixels (default 0, indicating no limit). The text value will be automatically truncated if the rendered size exceeds the limit.", + "type": "number" + }, + "opacity": { + "description": "The overall opacity (value between [0,1]).\n\n__Default value:__ `0.7` for non-aggregate plots with `point`, `tick`, `circle`, or `square` marks or layered `bar` charts and `1` otherwise.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "orient": { + "$ref": "#/definitions/Orient", + "description": "The orientation of a non-stacked bar, tick, area, and line charts.\nThe value is either horizontal (default) or vertical.\n- For bar, rule and tick, this determines whether the size of the bar and tick\nshould be applied to x or y dimension.\n- For area, this property determines the orient property of the Vega output.\n- For line, this property determines the sort order of the points in the line\nif `config.sortLineBy` is not specified.\nFor stacked charts, this is always determined by the orientation of the stack;\ntherefore explicitly specified value will be ignored." + }, + "radius": { + "description": "Polar coordinate radial offset, in pixels, of the text label from the origin determined by the `x` and `y` properties.", + "minimum": 0, + "type": "number" + }, + "shape": { + "description": "The default symbol shape to use. One of: `\"circle\"` (default), `\"square\"`, `\"cross\"`, `\"diamond\"`, `\"triangle-up\"`, or `\"triangle-down\"`, or a custom SVG path.\n\n__Default value:__ `\"circle\"`", + "type": "string" + }, + "size": { + "description": "The pixel area each the point/circle/square.\nFor example: in the case of circles, the radius is determined in part by the square root of the size value.\n\n__Default value:__ `30`", + "minimum": 0, + "type": "number" + }, + "stroke": { + "description": "Default Stroke Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "strokeDash": { + "description": "An array of alternating stroke, space lengths for creating dashed or dotted lines.", + "items": { + "type": "number" + }, + "type": "array" + }, + "strokeDashOffset": { + "description": "The offset (in pixels) into which to begin drawing with the stroke dash array.", + "type": "number" + }, + "strokeOpacity": { + "description": "The stroke opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "strokeWidth": { + "description": "The stroke width, in pixels.", + "minimum": 0, + "type": "number" + }, + "tension": { + "description": "Depending on the interpolation type, sets the tension parameter (for line and area marks).", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "text": { + "description": "Placeholder text if the `text` channel is not specified", + "type": "string" + }, + "theta": { + "description": "Polar coordinate angle, in radians, of the text label from the origin determined by the `x` and `y` properties. Values for `theta` follow the same convention of `arc` mark `startAngle` and `endAngle` properties: angles are measured in radians, with `0` indicating \"north\".", + "type": "number" + } + }, + "type": "object" + }, + "BinParams": { + "additionalProperties": false, + "description": "Binning properties or boolean flag for determining whether to bin data or not.", + "properties": { + "base": { + "description": "The number base to use for automatic bin determination (default is base 10).\n\n__Default value:__ `10`", + "type": "number" + }, + "divide": { + "description": "Scale factors indicating allowable subdivisions. The default value is [5, 2], which indicates that for base 10 numbers (the default base), the method may consider dividing bin sizes by 5 and/or 2. For example, for an initial step size of 10, the method can check if bin sizes of 2 (= 10/5), 5 (= 10/2), or 1 (= 10/(5*2)) might also satisfy the given constraints.\n\n__Default value:__ `[5, 2]`", + "items": { + "type": "number" + }, + "minItems": 1, + "type": "array" + }, + "extent": { + "description": "A two-element (`[min, max]`) array indicating the range of desired bin values.", + "items": { + "type": "number" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "maxbins": { + "description": "Maximum number of bins.\n\n__Default value:__ `6` for `row`, `column` and `shape` channels; `10` for other channels", + "minimum": 2, + "type": "number" + }, + "minstep": { + "description": "A minimum allowable step size (particularly useful for integer values).", + "type": "number" + }, + "nice": { + "description": "If true (the default), attempts to make the bin boundaries use human-friendly boundaries, such as multiples of ten.", + "type": "boolean" + }, + "step": { + "description": "An exact step size to use between bins.\n\n__Note:__ If provided, options such as maxbins will be ignored.", + "type": "number" + }, + "steps": { + "description": "An array of allowable step sizes to choose from.", + "items": { + "type": "number" + }, + "minItems": 1, + "type": "array" + } + }, + "type": "object" + }, + "BinTransform": { + "additionalProperties": false, + "properties": { + "as": { + "description": "The output fields at which to write the start and end bin values.", + "type": "string" + }, + "bin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/BinParams" + } + ], + "description": "An object indicating bin properties, or simply `true` for using default bin parameters." + }, + "field": { + "description": "The data field to bin.", + "type": "string" + } + }, + "required": [ + "bin", + "field", + "as" + ], + "type": "object" + }, + "BrushConfig": { + "additionalProperties": false, + "properties": { + "fill": { + "description": "The fill color of the interval mark.\n\n__Default value:__ `#333333`", + "type": "string" + }, + "fillOpacity": { + "description": "The fill opacity of the interval mark (a value between 0 and 1).\n\n__Default value:__ `0.125`", + "type": "number" + }, + "stroke": { + "description": "The stroke color of the interval mark.\n\n__Default value:__ `#ffffff`", + "type": "string" + }, + "strokeDash": { + "description": "An array of alternating stroke and space lengths,\nfor creating dashed or dotted lines.", + "items": { + "type": "number" + }, + "type": "array" + }, + "strokeDashOffset": { + "description": "The offset (in pixels) with which to begin drawing the stroke dash array.", + "type": "number" + }, + "strokeOpacity": { + "description": "The stroke opacity of the interval mark (a value between 0 and 1).", + "type": "number" + }, + "strokeWidth": { + "description": "The stroke width of the interval mark.", + "type": "number" + } + }, + "type": "object" + }, + "CalculateTransform": { + "additionalProperties": false, + "properties": { + "as": { + "description": "The field for storing the computed formula value.", + "type": "string" + }, + "calculate": { + "description": "A string containing a Vega Expression. Use the variable `datum` to refer to the current data object.", + "type": "string" + } + }, + "required": [ + "calculate", + "as" + ], + "type": "object" + }, + "CompositeUnitSpec": { + "$ref": "#/definitions/CompositeUnitSpecAlias", + "description": "Unit spec that can have a composite mark." + }, + "Conditional": { + "additionalProperties": false, + "properties": { + "aggregate": { + "$ref": "#/definitions/Aggregate", + "description": "Aggregation function for the field\n(e.g., `mean`, `sum`, `median`, `min`, `max`, `count`).\n\n__Default value:__ `undefined` (None)" + }, + "bin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/BinParams" + } + ], + "description": "A flag for binning a `quantitative` field, or [an object defining binning parameters](bin.html#params).\nIf `true`, default [binning parameters](bin.html) will be applied.\n\n__Default value:__ `false`" + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RepeatRef" + } + ], + "description": "__Required.__ A string defining the name of the field from which to pull a data value\nor an object defining iterated values from the [`repeat`](repeat.html) operator.\n\n__Note:__ Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`).\nIf field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`).\nSee more details about escaping in the [field documentation](field.html).\n\n__Note:__ `field` is not required if `aggregate` is `count`." + }, + "legend": { + "anyOf": [ + { + "$ref": "#/definitions/Legend" + }, + { + "type": "null" + } + ], + "description": "An object defining properties of the legend.\nIf `null`, the legend for the encoding channel will be removed.\n\n__Default value:__ If undefined, default [legend properties](legend.html) are applied." + }, + "scale": { + "$ref": "#/definitions/Scale", + "description": "An object defining properties of the channel's scale, which is the function that transforms values in the data domain (numbers, dates, strings, etc) to visual values (pixels, colors, sizes) of the encoding channels.\n\n__Default value:__ If undefined, default [scale properties](scale.html) are applied." + }, + "selection": { + "$ref": "#/definitions/SelectionOperand", + "description": "A [selection name](selection.html), or a series of [composed selections](selection.html#compose)." + }, + "sort": { + "anyOf": [ + { + "$ref": "#/definitions/SortOrder" + }, + { + "$ref": "#/definitions/SortField" + }, + { + "type": "null" + } + ], + "description": "Sort order for the encoded field.\nSupported `sort` values include `\"ascending\"`, `\"descending\"` and `null` (no sorting).\nFor fields with discrete domains, `sort` can also be a [sort field definition object](sort.html#sort-field).\n\n__Default value:__ `\"ascending\"`" + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit (e.g., `year`, `yearmonth`, `month`, `hours`) for a temporal field.\nor [a temporal field that gets casted as ordinal](type.html#cast).\n\n__Default value:__ `undefined` (None)" + }, + "type": { + "$ref": "#/definitions/Type", + "description": "The encoded field's type of measurement (`\"quantitative\"`, `\"temporal\"`, `\"ordinal\"`, or `\"nominal\"`)." + } + }, + "required": [ + "selection", + "type" + ], + "type": "object" + }, + "Conditional": { + "additionalProperties": false, + "properties": { + "aggregate": { + "$ref": "#/definitions/Aggregate", + "description": "Aggregation function for the field\n(e.g., `mean`, `sum`, `median`, `min`, `max`, `count`).\n\n__Default value:__ `undefined` (None)" + }, + "bin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/BinParams" + } + ], + "description": "A flag for binning a `quantitative` field, or [an object defining binning parameters](bin.html#params).\nIf `true`, default [binning parameters](bin.html) will be applied.\n\n__Default value:__ `false`" + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RepeatRef" + } + ], + "description": "__Required.__ A string defining the name of the field from which to pull a data value\nor an object defining iterated values from the [`repeat`](repeat.html) operator.\n\n__Note:__ Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`).\nIf field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`).\nSee more details about escaping in the [field documentation](field.html).\n\n__Note:__ `field` is not required if `aggregate` is `count`." + }, + "format": { + "description": "The [formatting pattern](format.html) for a text field. If not defined, this will be determined automatically.", + "type": "string" + }, + "selection": { + "$ref": "#/definitions/SelectionOperand", + "description": "A [selection name](selection.html), or a series of [composed selections](selection.html#compose)." + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit (e.g., `year`, `yearmonth`, `month`, `hours`) for a temporal field.\nor [a temporal field that gets casted as ordinal](type.html#cast).\n\n__Default value:__ `undefined` (None)" + }, + "type": { + "$ref": "#/definitions/Type", + "description": "The encoded field's type of measurement (`\"quantitative\"`, `\"temporal\"`, `\"ordinal\"`, or `\"nominal\"`)." + } + }, + "required": [ + "selection", + "type" + ], + "type": "object" + }, + "Conditional": { + "additionalProperties": false, + "properties": { + "selection": { + "$ref": "#/definitions/SelectionOperand", + "description": "A [selection name](selection.html), or a series of [composed selections](selection.html#compose)." + }, + "value": { + "description": "A constant value in visual domain (e.g., `\"red\"` / \"#0099ff\" for color, values between `0` to `1` for opacity).", + "type": [ + "number", + "string", + "boolean" + ] + } + }, + "required": [ + "selection", + "value" + ], + "type": "object" + }, + "Config": { + "additionalProperties": false, + "properties": { + "area": { + "$ref": "#/definitions/MarkConfig", + "description": "Area-Specific Config " + }, + "autosize": { + "anyOf": [ + { + "$ref": "#/definitions/AutosizeType" + }, + { + "$ref": "#/definitions/AutoSizeParams" + } + ], + "description": "Sets how the visualization size should be determined. If a string, should be one of `\"pad\"`, `\"fit\"` or `\"none\"`.\nObject values can additionally specify parameters for content sizing and automatic resizing.\n`\"fit\"` is only supported for single and layered views that don't use `rangeStep`.\n\n__Default value__: `pad`" + }, + "axis": { + "$ref": "#/definitions/AxisConfig", + "description": "Axis configuration, which determines default properties for all `x` and `y` [axes](axis.html). For a full list of axis configuration options, please see the [corresponding section of the axis documentation](axis.html#config)." + }, + "axisBand": { + "$ref": "#/definitions/VgAxisConfig", + "description": "Specific axis config for axes with \"band\" scales." + }, + "axisBottom": { + "$ref": "#/definitions/VgAxisConfig", + "description": "Specific axis config for x-axis along the bottom edge of the chart." + }, + "axisLeft": { + "$ref": "#/definitions/VgAxisConfig", + "description": "Specific axis config for y-axis along the left edge of the chart." + }, + "axisRight": { + "$ref": "#/definitions/VgAxisConfig", + "description": "Specific axis config for y-axis along the right edge of the chart." + }, + "axisTop": { + "$ref": "#/definitions/VgAxisConfig", + "description": "Specific axis config for x-axis along the top edge of the chart." + }, + "axisX": { + "$ref": "#/definitions/VgAxisConfig", + "description": "X-axis specific config." + }, + "axisY": { + "$ref": "#/definitions/VgAxisConfig", + "description": "Y-axis specific config." + }, + "background": { + "description": "CSS color property to use as the background of visualization.\n\n__Default value:__ none (transparent)", + "type": "string" + }, + "bar": { + "$ref": "#/definitions/BarConfig", + "description": "Bar-Specific Config " + }, + "circle": { + "$ref": "#/definitions/MarkConfig", + "description": "Circle-Specific Config " + }, + "countTitle": { + "description": "Default axis and legend title for count fields.\n\n__Default value:__ `'Number of Records'`.", + "type": "string" + }, + "fieldTitle": { + "description": "Defines how Vega-Lite generates title for fields. There are three possible styles:\n- `\"verbal\"` (Default) - displays function in a verbal style (e.g., \"Sum of field\", \"Year-month of date\", \"field (binned)\").\n- `\"function\"` - displays function using parentheses and capitalized texts (e.g., \"SUM(field)\", \"YEARMONTH(date)\", \"BIN(field)\").\n- `\"plain\"` - displays only the field name without functions (e.g., \"field\", \"date\", \"field\").", + "enum": [ + "verbal", + "functional", + "plain" + ], + "type": "string" + }, + "invalidValues": { + "description": "Defines how Vega-Lite should handle invalid values (`null` and `NaN`).\n- If set to `\"filter\"` (default), all data items with null values are filtered.\n- If `null`, all data items are included. In this case, invalid values will be interpreted as zeroes.", + "enum": [ + "filter", + null + ], + "type": [ + "string", + "null" + ] + }, + "legend": { + "$ref": "#/definitions/LegendConfig", + "description": "Legend configuration, which determines default properties for all [legends](legend.html). For a full list of legend configuration options, please see the [corresponding section of in the legend documentation](legend.html#config)." + }, + "line": { + "$ref": "#/definitions/MarkConfig", + "description": "Line-Specific Config " + }, + "mark": { + "$ref": "#/definitions/MarkConfig", + "description": "Mark Config " + }, + "numberFormat": { + "description": "D3 Number format for axis labels and text tables. For example \"s\" for SI units. Use [D3's number format pattern](https://github.com/d3/d3-format#locale_format).", + "type": "string" + }, + "padding": { + "$ref": "#/definitions/Padding", + "description": "The default visualization padding, in pixels, from the edge of the visualization canvas to the data rectangle. If a number, specifies padding for all sides.\nIf an object, the value should have the format `{\"left\": 5, \"top\": 5, \"right\": 5, \"bottom\": 5}` to specify padding for each side of the visualization.\n\n__Default value__: `5`" + }, + "point": { + "$ref": "#/definitions/MarkConfig", + "description": "Point-Specific Config " + }, + "range": { + "$ref": "#/definitions/RangeConfig", + "description": "An object hash that defines default range arrays or schemes for using with scales.\nFor a full list of scale range configuration options, please see the [corresponding section of the scale documentation](scale.html#config)." + }, + "rect": { + "$ref": "#/definitions/MarkConfig", + "description": "Rect-Specific Config " + }, + "rule": { + "$ref": "#/definitions/MarkConfig", + "description": "Rule-Specific Config " + }, + "scale": { + "$ref": "#/definitions/ScaleConfig", + "description": "Scale configuration determines default properties for all [scales](scale.html). For a full list of scale configuration options, please see the [corresponding section of the scale documentation](scale.html#config)." + }, + "selection": { + "$ref": "#/definitions/SelectionConfig", + "description": "An object hash for defining default properties for each type of selections. " + }, + "square": { + "$ref": "#/definitions/MarkConfig", + "description": "Square-Specific Config " + }, + "stack": { + "$ref": "#/definitions/StackOffset", + "description": "Default stack offset for stackable mark. " + }, + "style": { + "$ref": "#/definitions/StyleConfigIndex", + "description": "An object hash that defines key-value mappings to determine default properties for marks with a given [style](mark.html#mark-def). The keys represent styles names; the value are valid [mark configuration objects](mark.html#config). " + }, + "text": { + "$ref": "#/definitions/TextConfig", + "description": "Text-Specific Config " + }, + "tick": { + "$ref": "#/definitions/TickConfig", + "description": "Tick-Specific Config " + }, + "timeFormat": { + "description": "Default datetime format for axis and legend labels. The format can be set directly on each axis and legend. Use [D3's time format pattern](https://github.com/d3/d3-time-format#locale_format).\n\n__Default value:__ `'%b %d, %Y'`.", + "type": "string" + }, + "title": { + "$ref": "#/definitions/VgTitleConfig", + "description": "Title configuration, which determines default properties for all [titles](title.html). For a full list of title configuration options, please see the [corresponding section of the title documentation](title.html#config)." + }, + "view": { + "$ref": "#/definitions/ViewConfig", + "description": "Default properties for [single view plots](spec.html#single). " + } + }, + "type": "object" + }, + "CsvDataFormat": { + "additionalProperties": false, + "properties": { + "parse": { + "anyOf": [ + { + "enum": [ + "auto" + ], + "type": "string" + }, + { + "type": "object" + } + ], + "description": "If set to auto (the default), perform automatic type inference to determine the desired data types.\nAlternatively, a parsing directive object can be provided for explicit data types. Each property of the object corresponds to a field name, and the value to the desired data type (one of `\"number\"`, `\"boolean\"` or `\"date\"`).\nFor example, `\"parse\": {\"modified_on\": \"date\"}` parses the `modified_on` field in each input record a Date value.\n\nFor `\"date\"`, we parse data based using Javascript's [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse).\nFor Specific date formats can be provided (e.g., `{foo: 'date:\"%m%d%Y\"'}`), using the [d3-time-format syntax](https://github.com/d3/d3-time-format#locale_format). UTC date format parsing is supported similarly (e.g., `{foo: 'utc:\"%m%d%Y\"'}`). See more about [UTC time](timeunit.html#utc)" + }, + "type": { + "description": "Type of input data: `\"json\"`, `\"csv\"`, `\"tsv\"`.\nThe default format type is determined by the extension of the file URL.\nIf no extension is detected, `\"json\"` will be used by default.", + "enum": [ + "csv", + "tsv" + ], + "type": "string" + } + }, + "type": "object" + }, + "Data": { + "anyOf": [ + { + "$ref": "#/definitions/UrlData" + }, + { + "$ref": "#/definitions/InlineData" + }, + { + "$ref": "#/definitions/NamedData" + } + ] + }, + "DataFormat": { + "anyOf": [ + { + "$ref": "#/definitions/CsvDataFormat" + }, + { + "$ref": "#/definitions/JsonDataFormat" + }, + { + "$ref": "#/definitions/TopoDataFormat" + } + ] + }, + "DateTime": { + "additionalProperties": false, + "description": "Object for defining datetime in Vega-Lite Filter.\nIf both month and quarter are provided, month has higher precedence.\n`day` cannot be combined with other date.\nWe accept string for month and day names.", + "properties": { + "date": { + "description": "Integer value representing the date from 1-31.", + "maximum": 31, + "minimum": 1, + "type": "number" + }, + "day": { + "anyOf": [ + { + "$ref": "#/definitions/Day" + }, + { + "type": "string" + } + ], + "description": "Value representing the day of a week. This can be one of: (1) integer value -- `1` represents Monday; (2) case-insensitive day name (e.g., `\"Monday\"`); (3) case-insensitive, 3-character short day name (e.g., `\"Mon\"`).
**Warning:** A DateTime definition object with `day`** should not be combined with `year`, `quarter`, `month`, or `date`." + }, + "hours": { + "description": "Integer value representing the hour of a day from 0-23.", + "maximum": 23, + "minimum": 0, + "type": "number" + }, + "milliseconds": { + "description": "Integer value representing the millisecond segment of time.", + "maximum": 999, + "minimum": 0, + "type": "number" + }, + "minutes": { + "description": "Integer value representing the minute segment of time from 0-59.", + "maximum": 59, + "minimum": 0, + "type": "number" + }, + "month": { + "anyOf": [ + { + "$ref": "#/definitions/Month" + }, + { + "type": "string" + } + ], + "description": "One of: (1) integer value representing the month from `1`-`12`. `1` represents January; (2) case-insensitive month name (e.g., `\"January\"`); (3) case-insensitive, 3-character short month name (e.g., `\"Jan\"`). " + }, + "quarter": { + "description": "Integer value representing the quarter of the year (from 1-4).", + "maximum": 4, + "minimum": 1, + "type": "number" + }, + "seconds": { + "description": "Integer value representing the second segment (0-59) of a time value", + "maximum": 59, + "minimum": 0, + "type": "number" + }, + "utc": { + "description": "A boolean flag indicating if date time is in utc time. If false, the date time is in local time", + "type": "boolean" + }, + "year": { + "description": "Integer value representing the year.", + "type": "number" + } + }, + "type": "object" + }, + "Day": { + "maximum": 7, + "minimum": 1, + "type": "number" + }, + "Encoding": { + "additionalProperties": false, + "properties": { + "color": { + "anyOf": [ + { + "$ref": "#/definitions/MarkPropFieldDefWithCondition" + }, + { + "$ref": "#/definitions/MarkPropValueDefWithCondition" + } + ], + "description": "Color of the marks – either fill or stroke color based on mark type.\nBy default, `color` represents fill color for `\"area\"`, `\"bar\"`, `\"tick\"`,\n`\"text\"`, `\"circle\"`, and `\"square\"` / stroke color for `\"line\"` and `\"point\"`.\n\n__Default value:__ If undefined, the default color depends on [mark config](config.html#mark)'s `color` property.\n\n_Note:_ See the scale documentation for more information about customizing [color scheme](scale.html#scheme)." + }, + "detail": { + "anyOf": [ + { + "$ref": "#/definitions/FieldDef" + }, + { + "items": { + "$ref": "#/definitions/FieldDef" + }, + "type": "array" + } + ], + "description": "Additional levels of detail for grouping data in aggregate views and\nin line and area marks without mapping data to a specific visual channel." + }, + "opacity": { + "anyOf": [ + { + "$ref": "#/definitions/MarkPropFieldDefWithCondition" + }, + { + "$ref": "#/definitions/MarkPropValueDefWithCondition" + } + ], + "description": "Opacity of the marks – either can be a value or a range.\n\n__Default value:__ If undefined, the default opacity depends on [mark config](config.html#mark)'s `opacity` property." + }, + "order": { + "anyOf": [ + { + "$ref": "#/definitions/OrderFieldDef" + }, + { + "items": { + "$ref": "#/definitions/OrderFieldDef" + }, + "type": "array" + } + ], + "description": "Stack order for stacked marks or order of data points in line marks for connected scatter plots.\n\n__Note__: In aggregate plots, `order` field should be `aggregate`d to avoid creating additional aggregation grouping." + }, + "shape": { + "anyOf": [ + { + "$ref": "#/definitions/MarkPropFieldDefWithCondition" + }, + { + "$ref": "#/definitions/MarkPropValueDefWithCondition" + } + ], + "description": "The symbol's shape (only for `point` marks). The supported values are\n`\"circle\"` (default), `\"square\"`, `\"cross\"`, `\"diamond\"`, `\"triangle-up\"`,\nor `\"triangle-down\"`, or else a custom SVG path string.\n__Default value:__ If undefined, the default shape depends on [mark config](config.html#point-config)'s `shape` property." + }, + "size": { + "anyOf": [ + { + "$ref": "#/definitions/MarkPropFieldDefWithCondition" + }, + { + "$ref": "#/definitions/MarkPropValueDefWithCondition" + } + ], + "description": "Size of the mark.\n- For `\"point\"`, `\"square\"` and `\"circle\"`, – the symbol size, or pixel area of the mark.\n- For `\"bar\"` and `\"tick\"` – the bar and tick's size.\n- For `\"text\"` – the text's font size.\n- Size is currently unsupported for `\"line\"`, `\"area\"`, and `\"rect\"`." + }, + "text": { + "anyOf": [ + { + "$ref": "#/definitions/TextFieldDefWithCondition" + }, + { + "$ref": "#/definitions/TextValueDefWithCondition" + } + ], + "description": "Text of the `text` mark." + }, + "tooltip": { + "anyOf": [ + { + "$ref": "#/definitions/TextFieldDefWithCondition" + }, + { + "$ref": "#/definitions/TextValueDefWithCondition" + } + ], + "description": "The tooltip text to show upon mouse hover." + }, + "x": { + "anyOf": [ + { + "$ref": "#/definitions/PositionFieldDef" + }, + { + "$ref": "#/definitions/ValueDef" + } + ], + "description": "X coordinates of the marks, or width of horizontal `\"bar\"` and `\"area\"`." + }, + "x2": { + "anyOf": [ + { + "$ref": "#/definitions/FieldDef" + }, + { + "$ref": "#/definitions/ValueDef" + } + ], + "description": "X2 coordinates for ranged `\"area\"`, `\"bar\"`, `\"rect\"`, and `\"rule\"`." + }, + "y": { + "anyOf": [ + { + "$ref": "#/definitions/PositionFieldDef" + }, + { + "$ref": "#/definitions/ValueDef" + } + ], + "description": "Y coordinates of the marks, or height of vertical `\"bar\"` and `\"area\"`." + }, + "y2": { + "anyOf": [ + { + "$ref": "#/definitions/FieldDef" + }, + { + "$ref": "#/definitions/ValueDef" + } + ], + "description": "Y2 coordinates for ranged `\"area\"`, `\"bar\"`, `\"rect\"`, and `\"rule\"`." + } + }, + "type": "object" + }, + "EncodingWithFacet": { + "additionalProperties": false, + "properties": { + "color": { + "anyOf": [ + { + "$ref": "#/definitions/MarkPropFieldDefWithCondition" + }, + { + "$ref": "#/definitions/MarkPropValueDefWithCondition" + } + ], + "description": "Color of the marks – either fill or stroke color based on mark type.\nBy default, `color` represents fill color for `\"area\"`, `\"bar\"`, `\"tick\"`,\n`\"text\"`, `\"circle\"`, and `\"square\"` / stroke color for `\"line\"` and `\"point\"`.\n\n__Default value:__ If undefined, the default color depends on [mark config](config.html#mark)'s `color` property.\n\n_Note:_ See the scale documentation for more information about customizing [color scheme](scale.html#scheme)." + }, + "column": { + "$ref": "#/definitions/FacetFieldDef", + "description": "Horizontal facets for trellis plots." + }, + "detail": { + "anyOf": [ + { + "$ref": "#/definitions/FieldDef" + }, + { + "items": { + "$ref": "#/definitions/FieldDef" + }, + "type": "array" + } + ], + "description": "Additional levels of detail for grouping data in aggregate views and\nin line and area marks without mapping data to a specific visual channel." + }, + "opacity": { + "anyOf": [ + { + "$ref": "#/definitions/MarkPropFieldDefWithCondition" + }, + { + "$ref": "#/definitions/MarkPropValueDefWithCondition" + } + ], + "description": "Opacity of the marks – either can be a value or a range.\n\n__Default value:__ If undefined, the default opacity depends on [mark config](config.html#mark)'s `opacity` property." + }, + "order": { + "anyOf": [ + { + "$ref": "#/definitions/OrderFieldDef" + }, + { + "items": { + "$ref": "#/definitions/OrderFieldDef" + }, + "type": "array" + } + ], + "description": "Stack order for stacked marks or order of data points in line marks for connected scatter plots.\n\n__Note__: In aggregate plots, `order` field should be `aggregate`d to avoid creating additional aggregation grouping." + }, + "row": { + "$ref": "#/definitions/FacetFieldDef", + "description": "Vertical facets for trellis plots." + }, + "shape": { + "anyOf": [ + { + "$ref": "#/definitions/MarkPropFieldDefWithCondition" + }, + { + "$ref": "#/definitions/MarkPropValueDefWithCondition" + } + ], + "description": "The symbol's shape (only for `point` marks). The supported values are\n`\"circle\"` (default), `\"square\"`, `\"cross\"`, `\"diamond\"`, `\"triangle-up\"`,\nor `\"triangle-down\"`, or else a custom SVG path string.\n__Default value:__ If undefined, the default shape depends on [mark config](config.html#point-config)'s `shape` property." + }, + "size": { + "anyOf": [ + { + "$ref": "#/definitions/MarkPropFieldDefWithCondition" + }, + { + "$ref": "#/definitions/MarkPropValueDefWithCondition" + } + ], + "description": "Size of the mark.\n- For `\"point\"`, `\"square\"` and `\"circle\"`, – the symbol size, or pixel area of the mark.\n- For `\"bar\"` and `\"tick\"` – the bar and tick's size.\n- For `\"text\"` – the text's font size.\n- Size is currently unsupported for `\"line\"`, `\"area\"`, and `\"rect\"`." + }, + "text": { + "anyOf": [ + { + "$ref": "#/definitions/TextFieldDefWithCondition" + }, + { + "$ref": "#/definitions/TextValueDefWithCondition" + } + ], + "description": "Text of the `text` mark." + }, + "tooltip": { + "anyOf": [ + { + "$ref": "#/definitions/TextFieldDefWithCondition" + }, + { + "$ref": "#/definitions/TextValueDefWithCondition" + } + ], + "description": "The tooltip text to show upon mouse hover." + }, + "x": { + "anyOf": [ + { + "$ref": "#/definitions/PositionFieldDef" + }, + { + "$ref": "#/definitions/ValueDef" + } + ], + "description": "X coordinates of the marks, or width of horizontal `\"bar\"` and `\"area\"`." + }, + "x2": { + "anyOf": [ + { + "$ref": "#/definitions/FieldDef" + }, + { + "$ref": "#/definitions/ValueDef" + } + ], + "description": "X2 coordinates for ranged `\"area\"`, `\"bar\"`, `\"rect\"`, and `\"rule\"`." + }, + "y": { + "anyOf": [ + { + "$ref": "#/definitions/PositionFieldDef" + }, + { + "$ref": "#/definitions/ValueDef" + } + ], + "description": "Y coordinates of the marks, or height of vertical `\"bar\"` and `\"area\"`." + }, + "y2": { + "anyOf": [ + { + "$ref": "#/definitions/FieldDef" + }, + { + "$ref": "#/definitions/ValueDef" + } + ], + "description": "Y2 coordinates for ranged `\"area\"`, `\"bar\"`, `\"rect\"`, and `\"rule\"`." + } + }, + "type": "object" + }, + "EqualFilter": { + "additionalProperties": false, + "properties": { + "equal": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "$ref": "#/definitions/DateTime" + } + ], + "description": "The value that the field should be equal to." + }, + "field": { + "description": "Field to be filtered.", + "type": "string" + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit for the field to be filtered." + } + }, + "required": [ + "field", + "equal" + ], + "type": "object" + }, + "FacetFieldDef": { + "additionalProperties": false, + "properties": { + "aggregate": { + "$ref": "#/definitions/Aggregate", + "description": "Aggregation function for the field\n(e.g., `mean`, `sum`, `median`, `min`, `max`, `count`).\n\n__Default value:__ `undefined` (None)" + }, + "bin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/BinParams" + } + ], + "description": "A flag for binning a `quantitative` field, or [an object defining binning parameters](bin.html#params).\nIf `true`, default [binning parameters](bin.html) will be applied.\n\n__Default value:__ `false`" + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RepeatRef" + } + ], + "description": "__Required.__ A string defining the name of the field from which to pull a data value\nor an object defining iterated values from the [`repeat`](repeat.html) operator.\n\n__Note:__ Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`).\nIf field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`).\nSee more details about escaping in the [field documentation](field.html).\n\n__Note:__ `field` is not required if `aggregate` is `count`." + }, + "header": { + "$ref": "#/definitions/Header", + "description": "An object defining properties of a facet's header." + }, + "sort": { + "$ref": "#/definitions/SortOrder", + "description": "Sort order for a facet field.\nThis can be `\"ascending\"`, `\"descending\"`." + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit (e.g., `year`, `yearmonth`, `month`, `hours`) for a temporal field.\nor [a temporal field that gets casted as ordinal](type.html#cast).\n\n__Default value:__ `undefined` (None)" + }, + "type": { + "$ref": "#/definitions/Type", + "description": "The encoded field's type of measurement (`\"quantitative\"`, `\"temporal\"`, `\"ordinal\"`, or `\"nominal\"`)." + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "FacetMapping": { + "additionalProperties": false, + "properties": { + "column": { + "$ref": "#/definitions/FacetFieldDef", + "description": "Horizontal facets for trellis plots." + }, + "row": { + "$ref": "#/definitions/FacetFieldDef", + "description": "Vertical facets for trellis plots." + } + }, + "type": "object" + }, + "FieldDef": { + "additionalProperties": false, + "description": "Definition object for a data field, its type and transformation of an encoding channel.", + "properties": { + "aggregate": { + "$ref": "#/definitions/Aggregate", + "description": "Aggregation function for the field\n(e.g., `mean`, `sum`, `median`, `min`, `max`, `count`).\n\n__Default value:__ `undefined` (None)" + }, + "bin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/BinParams" + } + ], + "description": "A flag for binning a `quantitative` field, or [an object defining binning parameters](bin.html#params).\nIf `true`, default [binning parameters](bin.html) will be applied.\n\n__Default value:__ `false`" + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RepeatRef" + } + ], + "description": "__Required.__ A string defining the name of the field from which to pull a data value\nor an object defining iterated values from the [`repeat`](repeat.html) operator.\n\n__Note:__ Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`).\nIf field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`).\nSee more details about escaping in the [field documentation](field.html).\n\n__Note:__ `field` is not required if `aggregate` is `count`." + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit (e.g., `year`, `yearmonth`, `month`, `hours`) for a temporal field.\nor [a temporal field that gets casted as ordinal](type.html#cast).\n\n__Default value:__ `undefined` (None)" + }, + "type": { + "$ref": "#/definitions/Type", + "description": "The encoded field's type of measurement (`\"quantitative\"`, `\"temporal\"`, `\"ordinal\"`, or `\"nominal\"`)." + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "MarkPropFieldDefWithCondition": { + "additionalProperties": false, + "description": "A FieldDef with Condition\n{\n condition: {value: ...},\n field: ...,\n ...\n}", + "properties": { + "aggregate": { + "$ref": "#/definitions/Aggregate", + "description": "Aggregation function for the field\n(e.g., `mean`, `sum`, `median`, `min`, `max`, `count`).\n\n__Default value:__ `undefined` (None)" + }, + "bin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/BinParams" + } + ], + "description": "A flag for binning a `quantitative` field, or [an object defining binning parameters](bin.html#params).\nIf `true`, default [binning parameters](bin.html) will be applied.\n\n__Default value:__ `false`" + }, + "condition": { + "anyOf": [ + { + "$ref": "#/definitions/Conditional" + }, + { + "items": { + "$ref": "#/definitions/Conditional" + }, + "type": "array" + } + ], + "description": "One or more value definition(s) with a selection predicate.\n\n__Note:__ A field definition's `condition` property can only contain [value definitions](encoding.html#value-def)\nsince Vega-Lite only allows at mosty one encoded field per encoding channel." + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RepeatRef" + } + ], + "description": "__Required.__ A string defining the name of the field from which to pull a data value\nor an object defining iterated values from the [`repeat`](repeat.html) operator.\n\n__Note:__ Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`).\nIf field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`).\nSee more details about escaping in the [field documentation](field.html).\n\n__Note:__ `field` is not required if `aggregate` is `count`." + }, + "legend": { + "anyOf": [ + { + "$ref": "#/definitions/Legend" + }, + { + "type": "null" + } + ], + "description": "An object defining properties of the legend.\nIf `null`, the legend for the encoding channel will be removed.\n\n__Default value:__ If undefined, default [legend properties](legend.html) are applied." + }, + "scale": { + "$ref": "#/definitions/Scale", + "description": "An object defining properties of the channel's scale, which is the function that transforms values in the data domain (numbers, dates, strings, etc) to visual values (pixels, colors, sizes) of the encoding channels.\n\n__Default value:__ If undefined, default [scale properties](scale.html) are applied." + }, + "sort": { + "anyOf": [ + { + "$ref": "#/definitions/SortOrder" + }, + { + "$ref": "#/definitions/SortField" + }, + { + "type": "null" + } + ], + "description": "Sort order for the encoded field.\nSupported `sort` values include `\"ascending\"`, `\"descending\"` and `null` (no sorting).\nFor fields with discrete domains, `sort` can also be a [sort field definition object](sort.html#sort-field).\n\n__Default value:__ `\"ascending\"`" + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit (e.g., `year`, `yearmonth`, `month`, `hours`) for a temporal field.\nor [a temporal field that gets casted as ordinal](type.html#cast).\n\n__Default value:__ `undefined` (None)" + }, + "type": { + "$ref": "#/definitions/Type", + "description": "The encoded field's type of measurement (`\"quantitative\"`, `\"temporal\"`, `\"ordinal\"`, or `\"nominal\"`)." + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "TextFieldDefWithCondition": { + "additionalProperties": false, + "description": "A FieldDef with Condition\n{\n condition: {value: ...},\n field: ...,\n ...\n}", + "properties": { + "aggregate": { + "$ref": "#/definitions/Aggregate", + "description": "Aggregation function for the field\n(e.g., `mean`, `sum`, `median`, `min`, `max`, `count`).\n\n__Default value:__ `undefined` (None)" + }, + "bin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/BinParams" + } + ], + "description": "A flag for binning a `quantitative` field, or [an object defining binning parameters](bin.html#params).\nIf `true`, default [binning parameters](bin.html) will be applied.\n\n__Default value:__ `false`" + }, + "condition": { + "anyOf": [ + { + "$ref": "#/definitions/Conditional" + }, + { + "items": { + "$ref": "#/definitions/Conditional" + }, + "type": "array" + } + ], + "description": "One or more value definition(s) with a selection predicate.\n\n__Note:__ A field definition's `condition` property can only contain [value definitions](encoding.html#value-def)\nsince Vega-Lite only allows at mosty one encoded field per encoding channel." + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RepeatRef" + } + ], + "description": "__Required.__ A string defining the name of the field from which to pull a data value\nor an object defining iterated values from the [`repeat`](repeat.html) operator.\n\n__Note:__ Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`).\nIf field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`).\nSee more details about escaping in the [field documentation](field.html).\n\n__Note:__ `field` is not required if `aggregate` is `count`." + }, + "format": { + "description": "The [formatting pattern](format.html) for a text field. If not defined, this will be determined automatically.", + "type": "string" + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit (e.g., `year`, `yearmonth`, `month`, `hours`) for a temporal field.\nor [a temporal field that gets casted as ordinal](type.html#cast).\n\n__Default value:__ `undefined` (None)" + }, + "type": { + "$ref": "#/definitions/Type", + "description": "The encoded field's type of measurement (`\"quantitative\"`, `\"temporal\"`, `\"ordinal\"`, or `\"nominal\"`)." + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "Filter": { + "anyOf": [ + { + "$ref": "#/definitions/EqualFilter" + }, + { + "$ref": "#/definitions/RangeFilter" + }, + { + "$ref": "#/definitions/OneOfFilter" + }, + { + "$ref": "#/definitions/SelectionFilter" + }, + { + "type": "string" + } + ] + }, + "FilterTransform": { + "additionalProperties": false, + "properties": { + "filter": { + "$ref": "#/definitions/FilterOperand", + "description": "The `filter` property must be either (1) a filter object for [equal-filters](filter.html#equalfilter),\n[range-filters](filter.html#rangefilter), [one-of filters](filter.html#oneoffilter), or [selection filters](filter.html#selectionfilter);\n(2) a [Vega Expression](filter.html#expression) string,\nwhere `datum` can be used to refer to the current data object; or (3) an array of filters (either objects or expression strings) that must all be true for a datum to pass the filter and be included." + } + }, + "required": [ + "filter" + ], + "type": "object" + }, + "FontStyle": { + "enum": [ + "normal", + "italic" + ], + "type": "string" + }, + "FontWeight": { + "enum": [ + "normal", + "bold" + ], + "type": "string" + }, + "FontWeightNumber": { + "maximum": 900, + "minimum": 100, + "type": "number" + }, + "FacetSpec": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "facet": { + "$ref": "#/definitions/FacetMapping", + "description": "An object that describes mappings between `row` and `column` channels and their field definitions." + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale, axis, and legend resolutions for facets." + }, + "spec": { + "anyOf": [ + { + "$ref": "#/definitions/LayerSpec" + }, + { + "$ref": "#/definitions/CompositeUnitSpec" + } + ], + "description": "A specification of the view that gets faceted." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + } + }, + "required": [ + "facet", + "spec" + ], + "type": "object" + }, + "HConcatSpec": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "hconcat": { + "description": "A list of views that should be concatenated and put into a row.", + "items": { + "$ref": "#/definitions/Spec" + }, + "type": "array" + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale, axis, and legend resolutions for horizontally concatenated charts." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + } + }, + "required": [ + "hconcat" + ], + "type": "object" + }, + "LayerSpec": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "height": { + "description": "The height of a visualization.\n\n__Default value:__\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its y-channel has a [continuous scale](scale.html#continuous), the height will be the value of [`config.view.height`](spec.html#config).\n- For y-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the height is [determined by the range step, paddings, and the cardinality of the field mapped to y-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the height will be the value of [`config.view.height`](spec.html#config).\n- If no field is mapped to `y` channel, the `height` will be the value of `rangeStep`.\n\n__Note__: For plots with [`row` and `column` channels](encoding.html#facet), this represents the height of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + }, + "layer": { + "description": "Layer or single view specifications to be layered.\n\n__Note__: Specifications inside `layer` cannot use `row` and `column` channels as layering facet specifications is not allowed.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/LayerSpec" + }, + { + "$ref": "#/definitions/CompositeUnitSpec" + } + ] + }, + "type": "array" + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale, axis, and legend resolutions for layers." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + }, + "width": { + "description": "The width of a visualization.\n\n__Default value:__ This will be determined by the following rules:\n\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its x-channel has a [continuous scale](scale.html#continuous), the width will be the value of [`config.view.width`](spec.html#config).\n- For x-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the width is [determined by the range step, paddings, and the cardinality of the field mapped to x-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the width will be the value of [`config.view.width`](spec.html#config).\n- If no field is mapped to `x` channel, the `width` will be the value of [`config.scale.textXRangeStep`](size.html#default-width-and-height) for `text` mark and the value of `rangeStep` for other marks.\n\n__Note:__ For plots with [`row` and `column` channels](encoding.html#facet), this represents the width of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + } + }, + "required": [ + "layer" + ], + "type": "object" + }, + "RepeatSpec": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "repeat": { + "$ref": "#/definitions/Repeat", + "description": "An object that describes what fields should be repeated into views that are laid out as a `row` or `column`." + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale and legend resolutions for repeated charts." + }, + "spec": { + "$ref": "#/definitions/Spec" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + } + }, + "required": [ + "repeat", + "spec" + ], + "type": "object" + }, + "Spec": { + "anyOf": [ + { + "$ref": "#/definitions/CompositeUnitSpec" + }, + { + "$ref": "#/definitions/LayerSpec" + }, + { + "$ref": "#/definitions/FacetSpec" + }, + { + "$ref": "#/definitions/RepeatSpec" + }, + { + "$ref": "#/definitions/VConcatSpec" + }, + { + "$ref": "#/definitions/HConcatSpec" + } + ] + }, + "CompositeUnitSpecAlias": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "encoding": { + "$ref": "#/definitions/Encoding", + "description": "A key-value mapping between encoding channels and definition of fields." + }, + "height": { + "description": "The height of a visualization.\n\n__Default value:__\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its y-channel has a [continuous scale](scale.html#continuous), the height will be the value of [`config.view.height`](spec.html#config).\n- For y-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the height is [determined by the range step, paddings, and the cardinality of the field mapped to y-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the height will be the value of [`config.view.height`](spec.html#config).\n- If no field is mapped to `y` channel, the `height` will be the value of `rangeStep`.\n\n__Note__: For plots with [`row` and `column` channels](encoding.html#facet), this represents the height of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + }, + "mark": { + "$ref": "#/definitions/AnyMark", + "description": "A string describing the mark type (one of `\"bar\"`, `\"circle\"`, `\"square\"`, `\"tick\"`, `\"line\"`,\n`\"area\"`, `\"point\"`, `\"rule\"`, and `\"text\"`) or a [mark definition object](mark.html#mark-def)." + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "selection": { + "additionalProperties": { + "$ref": "#/definitions/SelectionDef" + }, + "description": "A key-value mapping between selection names and definitions.", + "type": "object" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + }, + "width": { + "description": "The width of a visualization.\n\n__Default value:__ This will be determined by the following rules:\n\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its x-channel has a [continuous scale](scale.html#continuous), the width will be the value of [`config.view.width`](spec.html#config).\n- For x-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the width is [determined by the range step, paddings, and the cardinality of the field mapped to x-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the width will be the value of [`config.view.width`](spec.html#config).\n- If no field is mapped to `x` channel, the `width` will be the value of [`config.scale.textXRangeStep`](size.html#default-width-and-height) for `text` mark and the value of `rangeStep` for other marks.\n\n__Note:__ For plots with [`row` and `column` channels](encoding.html#facet), this represents the width of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + } + }, + "required": [ + "mark", + "encoding" + ], + "type": "object" + }, + "FacetedCompositeUnitSpecAlias": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "encoding": { + "$ref": "#/definitions/EncodingWithFacet", + "description": "A key-value mapping between encoding channels and definition of fields." + }, + "height": { + "description": "The height of a visualization.\n\n__Default value:__\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its y-channel has a [continuous scale](scale.html#continuous), the height will be the value of [`config.view.height`](spec.html#config).\n- For y-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the height is [determined by the range step, paddings, and the cardinality of the field mapped to y-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the height will be the value of [`config.view.height`](spec.html#config).\n- If no field is mapped to `y` channel, the `height` will be the value of `rangeStep`.\n\n__Note__: For plots with [`row` and `column` channels](encoding.html#facet), this represents the height of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + }, + "mark": { + "$ref": "#/definitions/AnyMark", + "description": "A string describing the mark type (one of `\"bar\"`, `\"circle\"`, `\"square\"`, `\"tick\"`, `\"line\"`,\n`\"area\"`, `\"point\"`, `\"rule\"`, and `\"text\"`) or a [mark definition object](mark.html#mark-def)." + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "selection": { + "additionalProperties": { + "$ref": "#/definitions/SelectionDef" + }, + "description": "A key-value mapping between selection names and definitions.", + "type": "object" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + }, + "width": { + "description": "The width of a visualization.\n\n__Default value:__ This will be determined by the following rules:\n\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its x-channel has a [continuous scale](scale.html#continuous), the width will be the value of [`config.view.width`](spec.html#config).\n- For x-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the width is [determined by the range step, paddings, and the cardinality of the field mapped to x-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the width will be the value of [`config.view.width`](spec.html#config).\n- If no field is mapped to `x` channel, the `width` will be the value of [`config.scale.textXRangeStep`](size.html#default-width-and-height) for `text` mark and the value of `rangeStep` for other marks.\n\n__Note:__ For plots with [`row` and `column` channels](encoding.html#facet), this represents the width of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + } + }, + "required": [ + "mark", + "encoding" + ], + "type": "object" + }, + "VConcatSpec": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale, axis, and legend resolutions for vertically concatenated charts." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + }, + "vconcat": { + "description": "A list of views that should be concatenated and put into a column.", + "items": { + "$ref": "#/definitions/Spec" + }, + "type": "array" + } + }, + "required": [ + "vconcat" + ], + "type": "object" + }, + "Header": { + "additionalProperties": false, + "description": "Headers of row / column channels for faceted plots.", + "properties": { + "format": { + "description": "The formatting pattern for labels. This is D3's [number format pattern](https://github.com/d3/d3-format#locale_format) for quantitative fields and D3's [time format pattern](https://github.com/d3/d3-time-format#locale_format) for time field.\n\nSee the [format documentation](format.html) for more information.\n\n__Default value:__ derived from [numberFormat](config.html#format) config for quantitative fields and from [timeFormat](config.html#format) config for temporal fields.", + "type": "string" + }, + "labelAngle": { + "description": "The rotation angle of the header labels.\n\n__Default value:__ `0`.", + "maximum": 360, + "minimum": -360, + "type": "number" + }, + "title": { + "description": "A title for the field. If `null`, the title will be removed.\n\n__Default value:__ derived from the field's name and transformation function (`aggregate`, `bin` and `timeUnit`). If the field has an aggregate function, the function is displayed as a part of the title (e.g., `\"Sum of Profit\"`). If the field is binned or has a time unit applied, the applied function will be denoted in parentheses (e.g., `\"Profit (binned)\"`, `\"Transaction Date (year-month)\"`). Otherwise, the title is simply the field name.\n\n__Note__: You can customize the default field title format by providing the [`fieldTitle` property in the [config](config.html) or [`fieldTitle` function via the `compile` function's options](compile.html#field-title).", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "HorizontalAlign": { + "enum": [ + "left", + "right", + "center" + ], + "type": "string" + }, + "InlineData": { + "additionalProperties": false, + "properties": { + "format": { + "$ref": "#/definitions/DataFormat", + "description": "An object that specifies the format for parsing the data values." + }, + "values": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "type": "boolean" + }, + "type": "array" + }, + { + "items": { + "type": "object" + }, + "type": "array" + }, + { + "type": "string" + }, + { + "type": "object" + } + ], + "description": "The full data set, included inline. This can be an array of objects or primitive values or a string.\nArrays of primitive values are ingested as objects with a `data` property. Strings are parsed according to the specified format type." + } + }, + "required": [ + "values" + ], + "type": "object" + }, + "Interpolate": { + "enum": [ + "linear", + "linear-closed", + "step", + "step-before", + "step-after", + "basis", + "basis-open", + "basis-closed", + "cardinal", + "cardinal-open", + "cardinal-closed", + "bundle", + "monotone" + ], + "type": "string" + }, + "InterpolateParams": { + "additionalProperties": false, + "properties": { + "gamma": { + "type": "number" + }, + "type": { + "enum": [ + "rgb", + "cubehelix", + "cubehelix-long" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "IntervalSelection": { + "additionalProperties": false, + "properties": { + "bind": { + "description": "Establishes a two-way binding between the interval selection and the scales\nused within the same view. This allows a user to interactively pan and\nzoom the view.", + "enum": [ + "scales" + ], + "type": "string" + }, + "empty": { + "description": "By default, all data values are considered to lie within an empty selection.\nWhen set to `none`, empty selections contain no data values.", + "enum": [ + "all", + "none" + ], + "type": "string" + }, + "encodings": { + "description": "An array of encoding channels. The corresponding data field values\nmust match for a data tuple to fall within the selection.", + "items": { + "$ref": "#/definitions/SingleDefChannel" + }, + "type": "array" + }, + "fields": { + "description": "An array of field names whose values must match for a data tuple to\nfall within the selection.", + "items": { + "type": "string" + }, + "type": "array" + }, + "mark": { + "$ref": "#/definitions/BrushConfig", + "description": "An interval selection also adds a rectangle mark to depict the\nextents of the interval. The `mark` property can be used to customize the\nappearance of the mark." + }, + "on": { + "$ref": "#/definitions/VgEventStream", + "description": "A [Vega event stream](https://vega.github.io/vega/docs/event-streams/) (object or selector) that triggers the selection.\nFor interval selections, the event stream must specify a [start and end](https://vega.github.io/vega/docs/event-streams/#between-filters)." + }, + "resolve": { + "$ref": "#/definitions/SelectionResolution", + "description": "With layered and multi-view displays, a strategy that determines how\nselections' data queries are resolved when applied in a filter transform,\nconditional encoding rule, or scale domain." + }, + "translate": { + "description": "When truthy, allows a user to interactively move an interval selection\nback-and-forth. Can be `true`, `false` (to disable panning), or a\n[Vega event stream definition](https://vega.github.io/vega/docs/event-streams/)\nwhich must include a start and end event to trigger continuous panning.\n\n__Default value:__ `true`, which corresponds to\n`[mousedown, window:mouseup] > window:mousemove!` which corresponds to\nclicks and dragging within an interval selection to reposition it.", + "type": [ + "string", + "boolean" + ] + }, + "type": { + "enum": [ + "interval" + ], + "type": "string" + }, + "zoom": { + "description": "When truthy, allows a user to interactively resize an interval selection.\nCan be `true`, `false` (to disable zooming), or a [Vega event stream\ndefinition](https://vega.github.io/vega/docs/event-streams/). Currently,\nonly `wheel` events are supported.\n\n\n__Default value:__ `true`, which corresponds to `wheel!`.", + "type": [ + "string", + "boolean" + ] + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "IntervalSelectionConfig": { + "additionalProperties": false, + "properties": { + "bind": { + "description": "Establishes a two-way binding between the interval selection and the scales\nused within the same view. This allows a user to interactively pan and\nzoom the view.", + "enum": [ + "scales" + ], + "type": "string" + }, + "empty": { + "description": "By default, all data values are considered to lie within an empty selection.\nWhen set to `none`, empty selections contain no data values.", + "enum": [ + "all", + "none" + ], + "type": "string" + }, + "encodings": { + "description": "An array of encoding channels. The corresponding data field values\nmust match for a data tuple to fall within the selection.", + "items": { + "$ref": "#/definitions/SingleDefChannel" + }, + "type": "array" + }, + "fields": { + "description": "An array of field names whose values must match for a data tuple to\nfall within the selection.", + "items": { + "type": "string" + }, + "type": "array" + }, + "mark": { + "$ref": "#/definitions/BrushConfig", + "description": "An interval selection also adds a rectangle mark to depict the\nextents of the interval. The `mark` property can be used to customize the\nappearance of the mark." + }, + "on": { + "$ref": "#/definitions/VgEventStream", + "description": "A [Vega event stream](https://vega.github.io/vega/docs/event-streams/) (object or selector) that triggers the selection.\nFor interval selections, the event stream must specify a [start and end](https://vega.github.io/vega/docs/event-streams/#between-filters)." + }, + "resolve": { + "$ref": "#/definitions/SelectionResolution", + "description": "With layered and multi-view displays, a strategy that determines how\nselections' data queries are resolved when applied in a filter transform,\nconditional encoding rule, or scale domain." + }, + "translate": { + "description": "When truthy, allows a user to interactively move an interval selection\nback-and-forth. Can be `true`, `false` (to disable panning), or a\n[Vega event stream definition](https://vega.github.io/vega/docs/event-streams/)\nwhich must include a start and end event to trigger continuous panning.\n\n__Default value:__ `true`, which corresponds to\n`[mousedown, window:mouseup] > window:mousemove!` which corresponds to\nclicks and dragging within an interval selection to reposition it.", + "type": [ + "string", + "boolean" + ] + }, + "zoom": { + "description": "When truthy, allows a user to interactively resize an interval selection.\nCan be `true`, `false` (to disable zooming), or a [Vega event stream\ndefinition](https://vega.github.io/vega/docs/event-streams/). Currently,\nonly `wheel` events are supported.\n\n\n__Default value:__ `true`, which corresponds to `wheel!`.", + "type": [ + "string", + "boolean" + ] + } + }, + "type": "object" + }, + "JsonDataFormat": { + "additionalProperties": false, + "properties": { + "parse": { + "anyOf": [ + { + "enum": [ + "auto" + ], + "type": "string" + }, + { + "type": "object" + } + ], + "description": "If set to auto (the default), perform automatic type inference to determine the desired data types.\nAlternatively, a parsing directive object can be provided for explicit data types. Each property of the object corresponds to a field name, and the value to the desired data type (one of `\"number\"`, `\"boolean\"` or `\"date\"`).\nFor example, `\"parse\": {\"modified_on\": \"date\"}` parses the `modified_on` field in each input record a Date value.\n\nFor `\"date\"`, we parse data based using Javascript's [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse).\nFor Specific date formats can be provided (e.g., `{foo: 'date:\"%m%d%Y\"'}`), using the [d3-time-format syntax](https://github.com/d3/d3-time-format#locale_format). UTC date format parsing is supported similarly (e.g., `{foo: 'utc:\"%m%d%Y\"'}`). See more about [UTC time](timeunit.html#utc)" + }, + "property": { + "description": "The JSON property containing the desired data.\nThis parameter can be used when the loaded JSON file may have surrounding structure or meta-data.\nFor example `\"property\": \"values.features\"` is equivalent to retrieving `json.values.features`\nfrom the loaded JSON object.", + "type": "string" + }, + "type": { + "description": "Type of input data: `\"json\"`, `\"csv\"`, `\"tsv\"`.\nThe default format type is determined by the extension of the file URL.\nIf no extension is detected, `\"json\"` will be used by default.", + "enum": [ + "json" + ], + "type": "string" + } + }, + "type": "object" + }, + "Legend": { + "additionalProperties": false, + "description": "Properties of a legend or boolean flag for determining whether to show it.", + "properties": { + "entryPadding": { + "description": "Padding (in pixels) between legend entries in a symbol legend.", + "type": "number" + }, + "format": { + "description": "The formatting pattern for labels. This is D3's [number format pattern](https://github.com/d3/d3-format#locale_format) for quantitative fields and D3's [time format pattern](https://github.com/d3/d3-time-format#locale_format) for time field.\n\nSee the [format documentation](format.html) for more information.\n\n__Default value:__ derived from [numberFormat](config.html#format) config for quantitative fields and from [timeFormat](config.html#format) config for temporal fields.", + "type": "string" + }, + "offset": { + "description": "The offset, in pixels, by which to displace the legend from the edge of the enclosing group or data rectangle.\n\n__Default value:__ `0`", + "type": "number" + }, + "orient": { + "$ref": "#/definitions/LegendOrient", + "description": "The orientation of the legend, which determines how the legend is positioned within the scene. One of \"left\", \"right\", \"top-left\", \"top-right\", \"bottom-left\", \"bottom-right\", \"none\".\n\n__Default value:__ `\"right\"`" + }, + "padding": { + "description": "The padding, in pixels, between the legend and axis.", + "type": "number" + }, + "tickCount": { + "description": "The desired number of tick values for quantitative legends.", + "type": "number" + }, + "title": { + "description": "A title for the field. If `null`, the title will be removed.\n\n__Default value:__ derived from the field's name and transformation function (`aggregate`, `bin` and `timeUnit`). If the field has an aggregate function, the function is displayed as a part of the title (e.g., `\"Sum of Profit\"`). If the field is binned or has a time unit applied, the applied function will be denoted in parentheses (e.g., `\"Profit (binned)\"`, `\"Transaction Date (year-month)\"`). Otherwise, the title is simply the field name.\n\n__Note__: You can customize the default field title format by providing the [`fieldTitle` property in the [config](config.html) or [`fieldTitle` function via the `compile` function's options](compile.html#field-title).", + "type": [ + "string", + "null" + ] + }, + "type": { + "description": "The type of the legend. Use `\"symbol\"` to create a discrete legend and `\"gradient\"` for a continuous color gradient.\n\n__Default value:__ `\"gradient\"` for non-binned quantitative fields and temporal fields; `\"symbol\"` otherwise.", + "enum": [ + "symbol", + "gradient" + ], + "type": "string" + }, + "values": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "$ref": "#/definitions/DateTime" + }, + "type": "array" + } + ], + "description": "Explicitly set the visible legend values." + }, + "zindex": { + "description": "A non-positive integer indicating z-index of the legend.\nIf zindex is 0, legend should be drawn behind all chart elements.\nTo put them in front, use zindex = 1.", + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "LegendConfig": { + "additionalProperties": false, + "properties": { + "cornerRadius": { + "description": "Corner radius for the full legend.", + "type": "number" + }, + "entryPadding": { + "description": "Padding (in pixels) between legend entries in a symbol legend.", + "type": "number" + }, + "fillColor": { + "description": "Background fill color for the full legend.", + "type": "string" + }, + "gradientHeight": { + "description": "The height of the gradient, in pixels.", + "minimum": 0, + "type": "number" + }, + "gradientLabelBaseline": { + "description": "Text baseline for color ramp gradient labels.", + "type": "string" + }, + "gradientLabelLimit": { + "description": "The maximum allowed length in pixels of color ramp gradient labels.", + "type": "number" + }, + "gradientLabelOffset": { + "description": "Vertical offset in pixels for color ramp gradient labels.", + "type": "number" + }, + "gradientStrokeColor": { + "description": "The color of the gradient stroke, can be in hex color code or regular color name.", + "type": "string" + }, + "gradientStrokeWidth": { + "description": "The width of the gradient stroke, in pixels.", + "minimum": 0, + "type": "number" + }, + "gradientWidth": { + "description": "The width of the gradient, in pixels.", + "minimum": 0, + "type": "number" + }, + "labelAlign": { + "description": "The alignment of the legend label, can be left, middle or right.", + "type": "string" + }, + "labelBaseline": { + "description": "The position of the baseline of legend label, can be top, middle or bottom.", + "type": "string" + }, + "labelColor": { + "description": "The color of the legend label, can be in hex color code or regular color name.", + "type": "string" + }, + "labelFont": { + "description": "The font of the legend label.", + "type": "string" + }, + "labelFontSize": { + "description": "The font size of legend label.\n\n__Default value:__ `10`.", + "minimum": 0, + "type": "number" + }, + "labelLimit": { + "description": "Maximum allowed pixel width of axis tick labels.", + "type": "number" + }, + "labelOffset": { + "description": "The offset of the legend label.", + "minimum": 0, + "type": "number" + }, + "offset": { + "description": "The offset, in pixels, by which to displace the legend from the edge of the enclosing group or data rectangle.\n\n__Default value:__ `0`", + "type": "number" + }, + "orient": { + "$ref": "#/definitions/LegendOrient", + "description": "The orientation of the legend, which determines how the legend is positioned within the scene. One of \"left\", \"right\", \"top-left\", \"top-right\", \"bottom-left\", \"bottom-right\", \"none\".\n\n__Default value:__ `\"right\"`" + }, + "padding": { + "description": "The padding, in pixels, between the legend and axis.", + "type": "number" + }, + "shortTimeLabels": { + "description": "Whether month names and weekday names should be abbreviated.\n\n__Default value:__ `false`", + "type": "boolean" + }, + "strokeColor": { + "description": "Border stroke color for the full legend.", + "type": "string" + }, + "strokeDash": { + "description": "Border stroke dash pattern for the full legend.", + "items": { + "type": "number" + }, + "type": "array" + }, + "strokeWidth": { + "description": "Border stroke width for the full legend.", + "type": "number" + }, + "symbolColor": { + "description": "The color of the legend symbol,", + "type": "string" + }, + "symbolSize": { + "description": "The size of the legend symbol, in pixels.", + "minimum": 0, + "type": "number" + }, + "symbolStrokeWidth": { + "description": "The width of the symbol's stroke.", + "minimum": 0, + "type": "number" + }, + "symbolType": { + "description": "Default shape type (such as \"circle\") for legend symbols.", + "type": "string" + }, + "titleAlign": { + "description": "Horizontal text alignment for legend titles.", + "type": "string" + }, + "titleBaseline": { + "description": "Vertical text baseline for legend titles.", + "type": "string" + }, + "titleColor": { + "description": "The color of the legend title, can be in hex color code or regular color name.", + "type": "string" + }, + "titleFont": { + "description": "The font of the legend title.", + "type": "string" + }, + "titleFontSize": { + "description": "The font size of the legend title.", + "type": "number" + }, + "titleFontWeight": { + "description": "The font weight of the legend title.", + "type": [ + "string", + "number" + ] + }, + "titleLimit": { + "description": "Maximum allowed pixel width of axis titles.", + "type": "number" + }, + "titlePadding": { + "description": "The padding, in pixels, between title and legend.", + "type": "number" + } + }, + "type": "object" + }, + "LegendOrient": { + "enum": [ + "left", + "right", + "top-left", + "top-right", + "bottom-left", + "bottom-right", + "none" + ], + "type": "string" + }, + "LegendResolveMap": { + "additionalProperties": false, + "properties": { + "color": { + "$ref": "#/definitions/ResolveMode" + }, + "opacity": { + "$ref": "#/definitions/ResolveMode" + }, + "shape": { + "$ref": "#/definitions/ResolveMode" + }, + "size": { + "$ref": "#/definitions/ResolveMode" + } + }, + "type": "object" + }, + "LocalMultiTimeUnit": { + "enum": [ + "yearquarter", + "yearquartermonth", + "yearmonth", + "yearmonthdate", + "yearmonthdatehours", + "yearmonthdatehoursminutes", + "yearmonthdatehoursminutesseconds", + "quartermonth", + "monthdate", + "hoursminutes", + "hoursminutesseconds", + "minutesseconds", + "secondsmilliseconds" + ], + "type": "string" + }, + "LocalSingleTimeUnit": { + "enum": [ + "year", + "quarter", + "month", + "day", + "date", + "hours", + "minutes", + "seconds", + "milliseconds" + ], + "type": "string" + }, + "AndFilter": { + "additionalProperties": false, + "properties": { + "and": { + "items": { + "$ref": "#/definitions/FilterOperand" + }, + "type": "array" + } + }, + "required": [ + "and" + ], + "type": "object" + }, + "SelectionAnd": { + "additionalProperties": false, + "properties": { + "and": { + "items": { + "$ref": "#/definitions/SelectionOperand" + }, + "type": "array" + } + }, + "required": [ + "and" + ], + "type": "object" + }, + "NotFilter": { + "additionalProperties": false, + "properties": { + "not": { + "$ref": "#/definitions/FilterOperand" + } + }, + "required": [ + "not" + ], + "type": "object" + }, + "SelectionNot": { + "additionalProperties": false, + "properties": { + "not": { + "$ref": "#/definitions/SelectionOperand" + } + }, + "required": [ + "not" + ], + "type": "object" + }, + "FilterOperand": { + "anyOf": [ + { + "$ref": "#/definitions/NotFilter" + }, + { + "$ref": "#/definitions/AndFilter" + }, + { + "$ref": "#/definitions/OrFilter" + }, + { + "$ref": "#/definitions/Filter" + } + ] + }, + "SelectionOperand": { + "anyOf": [ + { + "$ref": "#/definitions/SelectionNot" + }, + { + "$ref": "#/definitions/SelectionAnd" + }, + { + "$ref": "#/definitions/SelectionOr" + }, + { + "type": "string" + } + ] + }, + "OrFilter": { + "additionalProperties": false, + "properties": { + "or": { + "items": { + "$ref": "#/definitions/FilterOperand" + }, + "type": "array" + } + }, + "required": [ + "or" + ], + "type": "object" + }, + "SelectionOr": { + "additionalProperties": false, + "properties": { + "or": { + "items": { + "$ref": "#/definitions/SelectionOperand" + }, + "type": "array" + } + }, + "required": [ + "or" + ], + "type": "object" + }, + "LookupData": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/definitions/Data", + "description": "Secondary data source to lookup in." + }, + "fields": { + "description": "Fields in foreign data to lookup.\nIf not specificied, the entire object is queried.", + "items": { + "type": "string" + }, + "type": "array" + }, + "key": { + "description": "Key in data to lookup.", + "type": "string" + } + }, + "required": [ + "data", + "key" + ], + "type": "object" + }, + "LookupTransform": { + "additionalProperties": false, + "properties": { + "as": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "The field or fields for storing the computed formula value.\nIf `from.fields` is specified, the transform will use the same names for `as`.\nIf `from.fields` is not specified, `as` has to be a string and we put the whole object into the data under the specified name." + }, + "default": { + "description": "The default value to use if lookup fails.\n\n__Default value:__ `null`", + "type": "string" + }, + "from": { + "$ref": "#/definitions/LookupData", + "description": "Secondary data reference." + }, + "lookup": { + "description": "Key in primary data source.", + "type": "string" + } + }, + "required": [ + "lookup", + "from" + ], + "type": "object" + }, + "Mark": { + "description": "All types of primitive marks.", + "enum": [ + "area", + "bar", + "line", + "point", + "text", + "tick", + "rect", + "rule", + "circle", + "square" + ], + "type": "string" + }, + "MarkConfig": { + "additionalProperties": false, + "properties": { + "align": { + "$ref": "#/definitions/HorizontalAlign", + "description": "The horizontal alignment of the text. One of `\"left\"`, `\"right\"`, `\"center\"`." + }, + "angle": { + "description": "The rotation angle of the text, in degrees.", + "maximum": 360, + "minimum": 0, + "type": "number" + }, + "baseline": { + "$ref": "#/definitions/VerticalAlign", + "description": "The vertical alignment of the text. One of `\"top\"`, `\"middle\"`, `\"bottom\"`.\n\n__Default value:__ `\"middle\"`" + }, + "color": { + "description": "Default color. Note that `fill` and `stroke` have higher precedence than `color` and will override `color`.\n\n__Default value:__ `\"#4682b4\"`\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "string" + }, + "dx": { + "description": "The horizontal offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "dy": { + "description": "The vertical offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "fill": { + "description": "Default Fill Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "fillOpacity": { + "description": "The fill opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "filled": { + "description": "Whether the mark's color should be used as fill color instead of stroke color.\n\n__Default value:__ `true` for all marks except `point` and `false` for `point`.\n\n__Applicable for:__ `bar`, `point`, `circle`, `square`, and `area` marks.\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "boolean" + }, + "font": { + "description": "The typeface to set the text in (e.g., `\"Helvetica Neue\"`).", + "type": "string" + }, + "fontSize": { + "description": "The font size, in pixels.", + "minimum": 0, + "type": "number" + }, + "fontStyle": { + "$ref": "#/definitions/FontStyle", + "description": "The font style (e.g., `\"italic\"`)." + }, + "fontWeight": { + "anyOf": [ + { + "$ref": "#/definitions/FontWeight" + }, + { + "$ref": "#/definitions/FontWeightNumber" + } + ], + "description": "The font weight (e.g., `\"bold\"`)." + }, + "interpolate": { + "$ref": "#/definitions/Interpolate", + "description": "The line interpolation method to use for line and area marks. One of the following:\n- `\"linear\"`: piecewise linear segments, as in a polyline.\n- `\"linear-closed\"`: close the linear segments to form a polygon.\n- `\"step\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"step-before\"`: alternate between vertical and horizontal segments, as in a step function.\n- `\"step-after\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"basis\"`: a B-spline, with control point duplication on the ends.\n- `\"basis-open\"`: an open B-spline; may not intersect the start or end.\n- `\"basis-closed\"`: a closed B-spline, as in a loop.\n- `\"cardinal\"`: a Cardinal spline, with control point duplication on the ends.\n- `\"cardinal-open\"`: an open Cardinal spline; may not intersect the start or end, but will intersect other control points.\n- `\"cardinal-closed\"`: a closed Cardinal spline, as in a loop.\n- `\"bundle\"`: equivalent to basis, except the tension parameter is used to straighten the spline.\n- `\"monotone\"`: cubic interpolation that preserves monotonicity in y." + }, + "limit": { + "description": "The maximum length of the text mark in pixels (default 0, indicating no limit). The text value will be automatically truncated if the rendered size exceeds the limit.", + "type": "number" + }, + "opacity": { + "description": "The overall opacity (value between [0,1]).\n\n__Default value:__ `0.7` for non-aggregate plots with `point`, `tick`, `circle`, or `square` marks or layered `bar` charts and `1` otherwise.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "orient": { + "$ref": "#/definitions/Orient", + "description": "The orientation of a non-stacked bar, tick, area, and line charts.\nThe value is either horizontal (default) or vertical.\n- For bar, rule and tick, this determines whether the size of the bar and tick\nshould be applied to x or y dimension.\n- For area, this property determines the orient property of the Vega output.\n- For line, this property determines the sort order of the points in the line\nif `config.sortLineBy` is not specified.\nFor stacked charts, this is always determined by the orientation of the stack;\ntherefore explicitly specified value will be ignored." + }, + "radius": { + "description": "Polar coordinate radial offset, in pixels, of the text label from the origin determined by the `x` and `y` properties.", + "minimum": 0, + "type": "number" + }, + "shape": { + "description": "The default symbol shape to use. One of: `\"circle\"` (default), `\"square\"`, `\"cross\"`, `\"diamond\"`, `\"triangle-up\"`, or `\"triangle-down\"`, or a custom SVG path.\n\n__Default value:__ `\"circle\"`", + "type": "string" + }, + "size": { + "description": "The pixel area each the point/circle/square.\nFor example: in the case of circles, the radius is determined in part by the square root of the size value.\n\n__Default value:__ `30`", + "minimum": 0, + "type": "number" + }, + "stroke": { + "description": "Default Stroke Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "strokeDash": { + "description": "An array of alternating stroke, space lengths for creating dashed or dotted lines.", + "items": { + "type": "number" + }, + "type": "array" + }, + "strokeDashOffset": { + "description": "The offset (in pixels) into which to begin drawing with the stroke dash array.", + "type": "number" + }, + "strokeOpacity": { + "description": "The stroke opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "strokeWidth": { + "description": "The stroke width, in pixels.", + "minimum": 0, + "type": "number" + }, + "tension": { + "description": "Depending on the interpolation type, sets the tension parameter (for line and area marks).", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "text": { + "description": "Placeholder text if the `text` channel is not specified", + "type": "string" + }, + "theta": { + "description": "Polar coordinate angle, in radians, of the text label from the origin determined by the `x` and `y` properties. Values for `theta` follow the same convention of `arc` mark `startAngle` and `endAngle` properties: angles are measured in radians, with `0` indicating \"north\".", + "type": "number" + } + }, + "type": "object" + }, + "MarkDef": { + "additionalProperties": false, + "properties": { + "align": { + "$ref": "#/definitions/HorizontalAlign", + "description": "The horizontal alignment of the text. One of `\"left\"`, `\"right\"`, `\"center\"`." + }, + "angle": { + "description": "The rotation angle of the text, in degrees.", + "maximum": 360, + "minimum": 0, + "type": "number" + }, + "baseline": { + "$ref": "#/definitions/VerticalAlign", + "description": "The vertical alignment of the text. One of `\"top\"`, `\"middle\"`, `\"bottom\"`.\n\n__Default value:__ `\"middle\"`" + }, + "clip": { + "description": "Whether a mark be clipped to the enclosing group’s width and height.", + "type": "boolean" + }, + "color": { + "description": "Default color. Note that `fill` and `stroke` have higher precedence than `color` and will override `color`.\n\n__Default value:__ `\"#4682b4\"`\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "string" + }, + "dx": { + "description": "The horizontal offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "dy": { + "description": "The vertical offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "fill": { + "description": "Default Fill Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "fillOpacity": { + "description": "The fill opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "filled": { + "description": "Whether the mark's color should be used as fill color instead of stroke color.\n\n__Default value:__ `true` for all marks except `point` and `false` for `point`.\n\n__Applicable for:__ `bar`, `point`, `circle`, `square`, and `area` marks.\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "boolean" + }, + "font": { + "description": "The typeface to set the text in (e.g., `\"Helvetica Neue\"`).", + "type": "string" + }, + "fontSize": { + "description": "The font size, in pixels.", + "minimum": 0, + "type": "number" + }, + "fontStyle": { + "$ref": "#/definitions/FontStyle", + "description": "The font style (e.g., `\"italic\"`)." + }, + "fontWeight": { + "anyOf": [ + { + "$ref": "#/definitions/FontWeight" + }, + { + "$ref": "#/definitions/FontWeightNumber" + } + ], + "description": "The font weight (e.g., `\"bold\"`)." + }, + "interpolate": { + "$ref": "#/definitions/Interpolate", + "description": "The line interpolation method to use for line and area marks. One of the following:\n- `\"linear\"`: piecewise linear segments, as in a polyline.\n- `\"linear-closed\"`: close the linear segments to form a polygon.\n- `\"step\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"step-before\"`: alternate between vertical and horizontal segments, as in a step function.\n- `\"step-after\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"basis\"`: a B-spline, with control point duplication on the ends.\n- `\"basis-open\"`: an open B-spline; may not intersect the start or end.\n- `\"basis-closed\"`: a closed B-spline, as in a loop.\n- `\"cardinal\"`: a Cardinal spline, with control point duplication on the ends.\n- `\"cardinal-open\"`: an open Cardinal spline; may not intersect the start or end, but will intersect other control points.\n- `\"cardinal-closed\"`: a closed Cardinal spline, as in a loop.\n- `\"bundle\"`: equivalent to basis, except the tension parameter is used to straighten the spline.\n- `\"monotone\"`: cubic interpolation that preserves monotonicity in y." + }, + "limit": { + "description": "The maximum length of the text mark in pixels (default 0, indicating no limit). The text value will be automatically truncated if the rendered size exceeds the limit.", + "type": "number" + }, + "opacity": { + "description": "The overall opacity (value between [0,1]).\n\n__Default value:__ `0.7` for non-aggregate plots with `point`, `tick`, `circle`, or `square` marks or layered `bar` charts and `1` otherwise.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "orient": { + "$ref": "#/definitions/Orient", + "description": "The orientation of a non-stacked bar, tick, area, and line charts.\nThe value is either horizontal (default) or vertical.\n- For bar, rule and tick, this determines whether the size of the bar and tick\nshould be applied to x or y dimension.\n- For area, this property determines the orient property of the Vega output.\n- For line, this property determines the sort order of the points in the line\nif `config.sortLineBy` is not specified.\nFor stacked charts, this is always determined by the orientation of the stack;\ntherefore explicitly specified value will be ignored." + }, + "radius": { + "description": "Polar coordinate radial offset, in pixels, of the text label from the origin determined by the `x` and `y` properties.", + "minimum": 0, + "type": "number" + }, + "shape": { + "description": "The default symbol shape to use. One of: `\"circle\"` (default), `\"square\"`, `\"cross\"`, `\"diamond\"`, `\"triangle-up\"`, or `\"triangle-down\"`, or a custom SVG path.\n\n__Default value:__ `\"circle\"`", + "type": "string" + }, + "size": { + "description": "The pixel area each the point/circle/square.\nFor example: in the case of circles, the radius is determined in part by the square root of the size value.\n\n__Default value:__ `30`", + "minimum": 0, + "type": "number" + }, + "stroke": { + "description": "Default Stroke Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "strokeDash": { + "description": "An array of alternating stroke, space lengths for creating dashed or dotted lines.", + "items": { + "type": "number" + }, + "type": "array" + }, + "strokeDashOffset": { + "description": "The offset (in pixels) into which to begin drawing with the stroke dash array.", + "type": "number" + }, + "strokeOpacity": { + "description": "The stroke opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "strokeWidth": { + "description": "The stroke width, in pixels.", + "minimum": 0, + "type": "number" + }, + "style": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "A string or array of strings indicating the name of custom styles to apply to the mark. A style is a named collection of mark property defaults defined within the [style configuration](mark.html#style-config). If style is an array, later styles will override earlier styles. Any [mark properties](encoding.html#mark-prop) explicitly defined within the `encoding` will override a style default.\n\n__Default value:__ The mark's name. For example, a bar mark will have style `\"bar\"` by default.\n__Note:__ Any specified style will augment the default style. For example, a bar mark with `\"style\": \"foo\"` will receive from `config.style.bar` and `config.style.foo` (the specified style `\"foo\"` has higher precedence)." + }, + "tension": { + "description": "Depending on the interpolation type, sets the tension parameter (for line and area marks).", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "text": { + "description": "Placeholder text if the `text` channel is not specified", + "type": "string" + }, + "theta": { + "description": "Polar coordinate angle, in radians, of the text label from the origin determined by the `x` and `y` properties. Values for `theta` follow the same convention of `arc` mark `startAngle` and `endAngle` properties: angles are measured in radians, with `0` indicating \"north\".", + "type": "number" + }, + "type": { + "$ref": "#/definitions/Mark", + "description": "The mark type.\nOne of `\"bar\"`, `\"circle\"`, `\"square\"`, `\"tick\"`, `\"line\"`,\n`\"area\"`, `\"point\"`, `\"rule\"`, and `\"text\"`." + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "Month": { + "maximum": 12, + "minimum": 1, + "type": "number" + }, + "MultiSelection": { + "additionalProperties": false, + "properties": { + "empty": { + "description": "By default, all data values are considered to lie within an empty selection.\nWhen set to `none`, empty selections contain no data values.", + "enum": [ + "all", + "none" + ], + "type": "string" + }, + "encodings": { + "description": "An array of encoding channels. The corresponding data field values\nmust match for a data tuple to fall within the selection.", + "items": { + "$ref": "#/definitions/SingleDefChannel" + }, + "type": "array" + }, + "fields": { + "description": "An array of field names whose values must match for a data tuple to\nfall within the selection.", + "items": { + "type": "string" + }, + "type": "array" + }, + "nearest": { + "description": "When true, an invisible voronoi diagram is computed to accelerate discrete\nselection. The data value _nearest_ the mouse cursor is added to the selection.\n\nSee the [nearest transform](nearest.html) documentation for more information.", + "type": "boolean" + }, + "on": { + "$ref": "#/definitions/VgEventStream", + "description": "A [Vega event stream](https://vega.github.io/vega/docs/event-streams/) (object or selector) that triggers the selection.\nFor interval selections, the event stream must specify a [start and end](https://vega.github.io/vega/docs/event-streams/#between-filters)." + }, + "resolve": { + "$ref": "#/definitions/SelectionResolution", + "description": "With layered and multi-view displays, a strategy that determines how\nselections' data queries are resolved when applied in a filter transform,\nconditional encoding rule, or scale domain." + }, + "toggle": { + "description": "Controls whether data values should be toggled or only ever inserted into\nmulti selections. Can be `true`, `false` (for insertion only), or a\n[Vega expression](https://vega.github.io/vega/docs/expressions/).\n\n__Default value:__ `true`, which corresponds to `event.shiftKey` (i.e.,\ndata values are toggled when a user interacts with the shift-key pressed).\n\nSee the [toggle transform](toggle.html) documentation for more information.", + "type": [ + "string", + "boolean" + ] + }, + "type": { + "enum": [ + "multi" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "MultiSelectionConfig": { + "additionalProperties": false, + "properties": { + "empty": { + "description": "By default, all data values are considered to lie within an empty selection.\nWhen set to `none`, empty selections contain no data values.", + "enum": [ + "all", + "none" + ], + "type": "string" + }, + "encodings": { + "description": "An array of encoding channels. The corresponding data field values\nmust match for a data tuple to fall within the selection.", + "items": { + "$ref": "#/definitions/SingleDefChannel" + }, + "type": "array" + }, + "fields": { + "description": "An array of field names whose values must match for a data tuple to\nfall within the selection.", + "items": { + "type": "string" + }, + "type": "array" + }, + "nearest": { + "description": "When true, an invisible voronoi diagram is computed to accelerate discrete\nselection. The data value _nearest_ the mouse cursor is added to the selection.\n\nSee the [nearest transform](nearest.html) documentation for more information.", + "type": "boolean" + }, + "on": { + "$ref": "#/definitions/VgEventStream", + "description": "A [Vega event stream](https://vega.github.io/vega/docs/event-streams/) (object or selector) that triggers the selection.\nFor interval selections, the event stream must specify a [start and end](https://vega.github.io/vega/docs/event-streams/#between-filters)." + }, + "resolve": { + "$ref": "#/definitions/SelectionResolution", + "description": "With layered and multi-view displays, a strategy that determines how\nselections' data queries are resolved when applied in a filter transform,\nconditional encoding rule, or scale domain." + }, + "toggle": { + "description": "Controls whether data values should be toggled or only ever inserted into\nmulti selections. Can be `true`, `false` (for insertion only), or a\n[Vega expression](https://vega.github.io/vega/docs/expressions/).\n\n__Default value:__ `true`, which corresponds to `event.shiftKey` (i.e.,\ndata values are toggled when a user interacts with the shift-key pressed).\n\nSee the [toggle transform](toggle.html) documentation for more information.", + "type": [ + "string", + "boolean" + ] + } + }, + "type": "object" + }, + "MultiTimeUnit": { + "anyOf": [ + { + "$ref": "#/definitions/LocalMultiTimeUnit" + }, + { + "$ref": "#/definitions/UtcMultiTimeUnit" + } + ] + }, + "NamedData": { + "additionalProperties": false, + "properties": { + "format": { + "$ref": "#/definitions/DataFormat", + "description": "An object that specifies the format for parsing the data." + }, + "name": { + "description": "Provide a placeholder name and bind data at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "NiceTime": { + "enum": [ + "second", + "minute", + "hour", + "day", + "week", + "month", + "year" + ], + "type": "string" + }, + "OneOfFilter": { + "additionalProperties": false, + "properties": { + "field": { + "description": "Field to be filtered", + "type": "string" + }, + "oneOf": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "items": { + "type": "boolean" + }, + "type": "array" + }, + { + "items": { + "$ref": "#/definitions/DateTime" + }, + "type": "array" + } + ], + "description": "A set of values that the `field`'s value should be a member of,\nfor a data item included in the filtered data." + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "time unit for the field to be filtered." + } + }, + "required": [ + "field", + "oneOf" + ], + "type": "object" + }, + "OrderFieldDef": { + "additionalProperties": false, + "properties": { + "aggregate": { + "$ref": "#/definitions/Aggregate", + "description": "Aggregation function for the field\n(e.g., `mean`, `sum`, `median`, `min`, `max`, `count`).\n\n__Default value:__ `undefined` (None)" + }, + "bin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/BinParams" + } + ], + "description": "A flag for binning a `quantitative` field, or [an object defining binning parameters](bin.html#params).\nIf `true`, default [binning parameters](bin.html) will be applied.\n\n__Default value:__ `false`" + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RepeatRef" + } + ], + "description": "__Required.__ A string defining the name of the field from which to pull a data value\nor an object defining iterated values from the [`repeat`](repeat.html) operator.\n\n__Note:__ Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`).\nIf field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`).\nSee more details about escaping in the [field documentation](field.html).\n\n__Note:__ `field` is not required if `aggregate` is `count`." + }, + "sort": { + "$ref": "#/definitions/SortOrder", + "description": "The sort order. One of `\"ascending\"` (default) or `\"descending\"`." + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit (e.g., `year`, `yearmonth`, `month`, `hours`) for a temporal field.\nor [a temporal field that gets casted as ordinal](type.html#cast).\n\n__Default value:__ `undefined` (None)" + }, + "type": { + "$ref": "#/definitions/Type", + "description": "The encoded field's type of measurement (`\"quantitative\"`, `\"temporal\"`, `\"ordinal\"`, or `\"nominal\"`)." + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "Orient": { + "enum": [ + "horizontal", + "vertical" + ], + "type": "string" + }, + "Padding": { + "anyOf": [ + { + "type": "number" + }, + { + "additionalProperties": false, + "properties": { + "bottom": { + "type": "number" + }, + "left": { + "type": "number" + }, + "right": { + "type": "number" + }, + "top": { + "type": "number" + } + }, + "type": "object" + } + ], + "minimum": 0 + }, + "PositionFieldDef": { + "additionalProperties": false, + "properties": { + "aggregate": { + "$ref": "#/definitions/Aggregate", + "description": "Aggregation function for the field\n(e.g., `mean`, `sum`, `median`, `min`, `max`, `count`).\n\n__Default value:__ `undefined` (None)" + }, + "axis": { + "anyOf": [ + { + "$ref": "#/definitions/Axis" + }, + { + "type": "null" + } + ], + "description": "An object defining properties of axis's gridlines, ticks and labels.\nIf `null`, the axis for the encoding channel will be removed.\n\n__Default value:__ If undefined, default [axis properties](axis.html) are applied." + }, + "bin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/BinParams" + } + ], + "description": "A flag for binning a `quantitative` field, or [an object defining binning parameters](bin.html#params).\nIf `true`, default [binning parameters](bin.html) will be applied.\n\n__Default value:__ `false`" + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RepeatRef" + } + ], + "description": "__Required.__ A string defining the name of the field from which to pull a data value\nor an object defining iterated values from the [`repeat`](repeat.html) operator.\n\n__Note:__ Dots (`.`) and brackets (`[` and `]`) can be used to access nested objects (e.g., `\"field\": \"foo.bar\"` and `\"field\": \"foo['bar']\"`).\nIf field names contain dots or brackets but are not nested, you can use `\\\\` to escape dots and brackets (e.g., `\"a\\\\.b\"` and `\"a\\\\[0\\\\]\"`).\nSee more details about escaping in the [field documentation](field.html).\n\n__Note:__ `field` is not required if `aggregate` is `count`." + }, + "scale": { + "$ref": "#/definitions/Scale", + "description": "An object defining properties of the channel's scale, which is the function that transforms values in the data domain (numbers, dates, strings, etc) to visual values (pixels, colors, sizes) of the encoding channels.\n\n__Default value:__ If undefined, default [scale properties](scale.html) are applied." + }, + "sort": { + "anyOf": [ + { + "$ref": "#/definitions/SortOrder" + }, + { + "$ref": "#/definitions/SortField" + }, + { + "type": "null" + } + ], + "description": "Sort order for the encoded field.\nSupported `sort` values include `\"ascending\"`, `\"descending\"` and `null` (no sorting).\nFor fields with discrete domains, `sort` can also be a [sort field definition object](sort.html#sort-field).\n\n__Default value:__ `\"ascending\"`" + }, + "stack": { + "anyOf": [ + { + "$ref": "#/definitions/StackOffset" + }, + { + "type": "null" + } + ], + "description": "Type of stacking offset if the field should be stacked.\n`stack` is only applicable for `x` and `y` channels with continuous domains.\nFor example, `stack` of `y` can be used to customize stacking for a vertical bar chart.\n\n`stack` can be one of the following values:\n- `\"zero\"`: stacking with baseline offset at zero value of the scale (for creating typical stacked [bar](stack.html#bar) and [area](stack.html#area) chart).\n- `\"normalize\"` - stacking with normalized domain (for creating [normalized stacked bar and area charts](stack.html#normalized).
\n-`\"center\"` - stacking with center baseline (for [streamgraph](stack.html#streamgraph)).\n- `null` - No-stacking. This will produce layered [bar](stack.html#layered-bar-chart) and area chart.\n\n__Default value:__ `zero` for plots with all of the following conditions are true:\n(1) the mark is `bar` or `area`;\n(2) the stacked measure channel (x or y) has a linear scale;\n(3) At least one of non-position channels mapped to an unaggregated field that is different from x and y. Otherwise, `null` by default." + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "Time unit (e.g., `year`, `yearmonth`, `month`, `hours`) for a temporal field.\nor [a temporal field that gets casted as ordinal](type.html#cast).\n\n__Default value:__ `undefined` (None)" + }, + "type": { + "$ref": "#/definitions/Type", + "description": "The encoded field's type of measurement (`\"quantitative\"`, `\"temporal\"`, `\"ordinal\"`, or `\"nominal\"`)." + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "RangeConfig": { + "additionalProperties": { + "$ref": "#/definitions/RangeConfigValue" + }, + "properties": { + "category": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "$ref": "#/definitions/VgScheme" + } + ], + "description": "Default range for _nominal_ (categorical) fields." + }, + "diverging": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "$ref": "#/definitions/VgScheme" + } + ], + "description": "Default range for diverging _quantitative_ fields." + }, + "heatmap": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "$ref": "#/definitions/VgScheme" + } + ], + "description": "Default range for _quantitative_ heatmaps." + }, + "ordinal": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "$ref": "#/definitions/VgScheme" + } + ], + "description": "Default range for _ordinal_ fields." + }, + "ramp": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "$ref": "#/definitions/VgScheme" + } + ], + "description": "Default range for _quantitative_ and _temporal_ fields." + }, + "symbol": { + "description": "Default range palette for the `shape` channel.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "RangeConfigValue": { + "anyOf": [ + { + "items": { + "type": [ + "number", + "string" + ] + }, + "type": "array" + }, + { + "$ref": "#/definitions/VgScheme" + }, + { + "additionalProperties": false, + "properties": { + "step": { + "type": "number" + } + }, + "required": [ + "step" + ], + "type": "object" + } + ] + }, + "RangeFilter": { + "additionalProperties": false, + "properties": { + "field": { + "description": "Field to be filtered", + "type": "string" + }, + "range": { + "description": "An array of inclusive minimum and maximum values\nfor a field value of a data item to be included in the filtered data.", + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/DateTime" + } + ] + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "time unit for the field to be filtered." + } + }, + "required": [ + "field", + "range" + ], + "type": "object" + }, + "Repeat": { + "additionalProperties": false, + "properties": { + "column": { + "description": "Horizontal repeated views.", + "items": { + "type": "string" + }, + "type": "array" + }, + "row": { + "description": "Vertical repeated views.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "RepeatRef": { + "additionalProperties": false, + "description": "Reference to a repeated value.", + "properties": { + "repeat": { + "enum": [ + "row", + "column" + ], + "type": "string" + } + }, + "required": [ + "repeat" + ], + "type": "object" + }, + "Resolve": { + "additionalProperties": false, + "description": "Defines how scales, axes, and legends from different specs should be combined. Resolve is a mapping from `scale`, `axis`, and `legend` to a mapping from channels to resolutions.", + "properties": { + "axis": { + "$ref": "#/definitions/AxisResolveMap" + }, + "legend": { + "$ref": "#/definitions/LegendResolveMap" + }, + "scale": { + "$ref": "#/definitions/ScaleResolveMap" + } + }, + "type": "object" + }, + "ResolveMode": { + "enum": [ + "independent", + "shared" + ], + "type": "string" + }, + "Scale": { + "additionalProperties": false, + "properties": { + "base": { + "description": "The logarithm base of the `log` scale (default `10`).", + "type": "number" + }, + "clamp": { + "description": "If `true`, values that exceed the data domain are clamped to either the minimum or maximum range value\n\n__Default value:__ derived from the [scale config](config.html#scale-config)'s `clamp` (`true` by default).", + "type": "boolean" + }, + "domain": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "type": "boolean" + }, + "type": "array" + }, + { + "items": { + "$ref": "#/definitions/DateTime" + }, + "type": "array" + }, + { + "enum": [ + "unaggregated" + ], + "type": "string" + }, + { + "$ref": "#/definitions/SelectionDomain" + } + ], + "description": "Customized domain values.\n\nFor _quantitative_ fields, `domain` can take the form of a two-element array with minimum and maximum values. [Piecewise scales](scale.html#piecewise) can be created by providing a `domain` with more than two entries.\nIf the input field is aggregated, `domain` can also be a string value `\"unaggregated\"`, indicating that the domain should include the raw data values prior to the aggregation.\n\nFor _temporal_ fields, `domain` can be a two-element array minimum and maximum values, in the form of either timestamps or the [DateTime definition objects](types.html#datetime).\n\nFor _ordinal_ and _nominal_ fields, `domain` can be an array that lists valid input values.\n\nThe `selection` property can be used to [interactively determine](selection.html#scale-domains) the scale domain." + }, + "exponent": { + "description": "The exponent of the `pow` scale.", + "type": "number" + }, + "interpolate": { + "anyOf": [ + { + "$ref": "#/definitions/Interpolate" + }, + { + "$ref": "#/definitions/InterpolateParams" + } + ], + "description": "The interpolation method for range values. By default, a general interpolator for numbers, dates, strings and colors (in RGB space) is used. For color ranges, this property allows interpolation in alternative color spaces. Legal values include `rgb`, `hsl`, `hsl-long`, `lab`, `hcl`, `hcl-long`, `cubehelix` and `cubehelix-long` ('-long' variants use longer paths in polar coordinate spaces). If object-valued, this property accepts an object with a string-valued _type_ property and an optional numeric _gamma_ property applicable to rgb and cubehelix interpolators. For more, see the [d3-interpolate documentation](https://github.com/d3/d3-interpolate).\n\n__Note:__ Sequential scales do not support `interpolate` as they have a fixed interpolator. Since Vega-Lite uses sequential scales for quantitative fields by default, you have to set the scale `type` to other quantitative scale type such as `\"linear\"` to customize `interpolate`." + }, + "nice": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "$ref": "#/definitions/NiceTime" + }, + { + "additionalProperties": false, + "properties": { + "interval": { + "type": "string" + }, + "step": { + "type": "number" + } + }, + "required": [ + "interval", + "step" + ], + "type": "object" + } + ], + "description": "Extending the domain so that it starts and ends on nice round values. This method typically modifies the scale’s domain, and may only extend the bounds to the nearest round value. Nicing is useful if the domain is computed from data and may be irregular. For example, for a domain of _[0.201479…, 0.996679…]_, a nice domain might be _[0.2, 1.0]_.\n\nFor quantitative scales such as linear, `nice` can be either a boolean flag or a number. If `nice` is a number, it will represent a desired tick count. This allows greater control over the step size used to extend the bounds, guaranteeing that the returned ticks will exactly cover the domain.\n\nFor temporal fields with time and utc scales, the `nice` value can be a string indicating the desired time interval. Legal values are `\"millisecond\"`, `\"second\"`, `\"minute\"`, `\"hour\"`, `\"day\"`, `\"week\"`, `\"month\"`, and `\"year\"`. Alternatively, `time` and `utc` scales can accept an object-valued interval specifier of the form `{\"interval\": \"month\", \"step\": 3}`, which includes a desired number of interval steps. Here, the domain would snap to quarter (Jan, Apr, Jul, Oct) boundaries.\n\n__Default value:__ `true` for unbinned _quantitative_ fields; `false` otherwise." + }, + "padding": { + "description": "For _[continuous](scale.html#continuous)_ scales, expands the scale domain to accommodate the specified number of pixels on each of the scale range. The scale range must represent pixels for this parameter to function as intended. Padding adjustment is performed prior to all other adjustments, including the effects of the zero, nice, domainMin, and domainMax properties.\n\nFor _[band](scale.html#band)_ scales, shortcut for setting `paddingInner` and `paddingOuter` to the same value.\n\nFor _[point](scale.html#point)_ scales, alias for `paddingOuter`.\n\n__Default value:__ For _continuous_ scales, derived from the [scale config](scale.html#config)'s `continuousPadding`.\nFor _band and point_ scales, see `paddingInner` and `paddingOuter`.", + "minimum": 0, + "type": "number" + }, + "paddingInner": { + "description": "The inner padding (spacing) within each band step of band scales, as a fraction of the step size. This value must lie in the range [0,1].\n\nFor point scale, this property is invalid as point scales do not have internal band widths (only step sizes between bands).\n\n__Default value:__ derived from the [scale config](scale.html#config)'s `bandPaddingInner`.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "paddingOuter": { + "description": "The outer padding (spacing) at the ends of the range of band and point scales,\nas a fraction of the step size. This value must lie in the range [0,1].\n\n__Default value:__ derived from the [scale config](scale.html#config)'s `bandPaddingOuter` for band scales and `pointPadding` for point scales.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "range": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "The range of the scale. One of:\n\n- A string indicating a [pre-defined named scale range](scale.html#range-config) (e.g., example, `\"symbol\"`, or `\"diverging\"`).\n\n- For [continuous scales](scale.html#continuous), two-element array indicating minimum and maximum values, or an array with more than two entries for specifying a [piecewise scale](scale.html#piecewise).\n\n- For [discrete](scale.html#discrete) and [discretizing](scale.html#discretizing) scales, an array of desired output values.\n\n__Notes:__\n\n1) For [sequential](scale.html#sequential), [ordinal](scale.html#ordinal), and discretizing color scales, you can also specify a color [`scheme`](scale.html#scheme) instead of `range`.\n\n2) Any directly specified `range` for `x` and `y` channels will be ignored. Range can be customized via the view's corresponding [size](size.html) (`width` and `height`) or via [range steps and paddings properties](#range-step) for [band](#band) and [point](#point) scales." + }, + "rangeStep": { + "description": "The distance between the starts of adjacent bands or points in [band](scale.html#band) and [point](scale.html#point) scales.\n\nIf `rangeStep` is `null` or if the view contains the scale's corresponding [size](size.html) (`width` for `x` scales and `height` for `y` scales), `rangeStep` will be automatically determined to fit the size of the view.\n\n__Default value:__ derived the [scale config](config.html#scale-config)'s `textXRangeStep` (`90` by default) for x-scales of `text` marks and `rangeStep` (`21` by default) for x-scales of other marks and y-scales.\n\n__Warning__: If `rangeStep` is `null` and the cardinality of the scale's domain is higher than `width` or `height`, the rangeStep might become less than one pixel and the mark might not appear correctly.", + "minimum": 0, + "type": [ + "number", + "null" + ] + }, + "round": { + "description": "If `true`, rounds numeric output values to integers. This can be helpful for snapping to the pixel grid.\n\n__Default value:__ `false`.", + "type": "boolean" + }, + "scheme": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/SchemeParams" + } + ], + "description": "A string indicating a color [scheme](scale.html#scheme) name (e.g., `\"category10\"` or `\"viridis\"`) or a [scheme parameter object](scale.html#scheme-params).\n\nDiscrete color schemes may be used with [discrete](scale.html#discrete) or [discretizing](scale.html#discretizing) scales. Continuous color schemes are intended for use with [sequential](scales.html#sequential) scales.\n\nFor the full list of supported scheme, please refer to the [Vega Scheme](https://vega.github.io/vega/docs/schemes/#reference) reference." + }, + "type": { + "$ref": "#/definitions/ScaleType", + "description": "The type of scale. Vega-Lite supports the following categories of scale types:\n\n1) [**Continuous Scales**](scale.html#continuous) -- mapping continuous domains to continuous output ranges ([`\"linear\"`](scale.html#linear), [`\"pow\"`](scale.html#pow), [`\"sqrt\"`](scale.html#sqrt), [`\"log\"`](scale.html#log), [`\"time\"`](scale.html#time), [`\"utc\"`](scale.html#utc), [`\"sequential\"`](scale.html#sequential)).\n\n2) [**Discrete Scales**](scale.html#discrete) -- mapping discrete domains to discrete ([`\"ordinal\"`](scale.html#ordinal)) or continuous ([`\"band\"`](scale.html#band) and [`\"point\"`](scale.html#point)) output ranges.\n\n3) [**Discretizing Scales**](scale.html#discretizing) -- mapping continuous domains to discrete output ranges ([`\"bin-linear\"`](scale.html#bin-linear) and [`\"bin-ordinal\"`](scale.html#bin-ordinal)).\n\n__Default value:__ please see the [scale type table](scale.html#type)." + }, + "zero": { + "description": "If `true`, ensures that a zero baseline value is included in the scale domain.\n\n__Default value:__ `true` for x and y channels if the quantitative field is not binned and no custom `domain` is provided; `false` otherwise.\n\n__Note:__ Log, time, and utc scales do not support `zero`.", + "type": "boolean" + } + }, + "type": "object" + }, + "ScaleConfig": { + "additionalProperties": false, + "properties": { + "bandPaddingInner": { + "description": "Default inner padding for `x` and `y` band-ordinal scales.\n\n__Default value:__ `0.1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "bandPaddingOuter": { + "description": "Default outer padding for `x` and `y` band-ordinal scales.\nIf not specified, by default, band scale's paddingOuter is paddingInner/2.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "clamp": { + "description": "If true, values that exceed the data domain are clamped to either the minimum or maximum range value", + "type": "boolean" + }, + "continuousPadding": { + "description": "Default padding for continuous scales.\n\n__Default:__ `5` for continuous x-scale of a vertical bar and continuous y-scale of a horizontal bar.; `0` otherwise.", + "minimum": 0, + "type": "number" + }, + "maxBandSize": { + "description": "The default max value for mapping quantitative fields to bar's size/bandSize.\n\nIf undefined (default), we will use the scale's `rangeStep` - 1.", + "minimum": 0, + "type": "number" + }, + "maxFontSize": { + "description": "The default max value for mapping quantitative fields to text's size/fontSize.\n\n__Default value:__ `40`", + "minimum": 0, + "type": "number" + }, + "maxOpacity": { + "description": "Default max opacity for mapping a field to opacity.\n\n__Default value:__ `0.8`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "maxSize": { + "description": "Default max value for point size scale.", + "minimum": 0, + "type": "number" + }, + "maxStrokeWidth": { + "description": "Default max strokeWidth for strokeWidth (or rule/line's size) scale.\n\n__Default value:__ `4`", + "minimum": 0, + "type": "number" + }, + "minBandSize": { + "description": "The default min value for mapping quantitative fields to bar and tick's size/bandSize scale with zero=false.\n\n__Default value:__ `2`", + "minimum": 0, + "type": "number" + }, + "minFontSize": { + "description": "The default min value for mapping quantitative fields to tick's size/fontSize scale with zero=false\n\n__Default value:__ `8`", + "minimum": 0, + "type": "number" + }, + "minOpacity": { + "description": "Default minimum opacity for mapping a field to opacity.\n\n__Default value:__ `0.3`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "minSize": { + "description": "Default minimum value for point size scale with zero=false.\n\n__Default value:__ `9`", + "minimum": 0, + "type": "number" + }, + "minStrokeWidth": { + "description": "Default minimum strokeWidth for strokeWidth (or rule/line's size) scale with zero=false.\n\n__Default value:__ `1`", + "minimum": 0, + "type": "number" + }, + "pointPadding": { + "description": "Default outer padding for `x` and `y` point-ordinal scales.\n\n__Default value:__ `0.5`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "rangeStep": { + "description": "Default range step for band and point scales of (1) the `y` channel\nand (2) the `x` channel when the mark is not `text`.\n\n__Default value:__ `21`", + "minimum": 0, + "type": [ + "number", + "null" + ] + }, + "round": { + "description": "If true, rounds numeric output values to integers.\nThis can be helpful for snapping to the pixel grid.\n(Only available for `x`, `y`, and `size` scales.)", + "type": "boolean" + }, + "textXRangeStep": { + "description": "Default range step for `x` band and point scales of text marks.\n\n__Default value:__ `90`", + "minimum": 0, + "type": "number" + }, + "useUnaggregatedDomain": { + "description": "Use the source data range before aggregation as scale domain instead of aggregated data for aggregate axis.\n\nThis is equivalent to setting `domain` to `\"unaggregate\"` for aggregated _quantitative_ fields by default.\n\nThis property only works with aggregate functions that produce values within the raw data domain (`\"mean\"`, `\"average\"`, `\"median\"`, `\"q1\"`, `\"q3\"`, `\"min\"`, `\"max\"`). For other aggregations that produce values outside of the raw data domain (e.g. `\"count\"`, `\"sum\"`), this property is ignored.\n\n__Default value:__ `false`", + "type": "boolean" + } + }, + "type": "object" + }, + "ScaleResolveMap": { + "additionalProperties": false, + "properties": { + "color": { + "$ref": "#/definitions/ResolveMode" + }, + "opacity": { + "$ref": "#/definitions/ResolveMode" + }, + "shape": { + "$ref": "#/definitions/ResolveMode" + }, + "size": { + "$ref": "#/definitions/ResolveMode" + }, + "x": { + "$ref": "#/definitions/ResolveMode" + }, + "y": { + "$ref": "#/definitions/ResolveMode" + } + }, + "type": "object" + }, + "ScaleType": { + "enum": [ + "linear", + "bin-linear", + "log", + "pow", + "sqrt", + "time", + "utc", + "sequential", + "ordinal", + "bin-ordinal", + "point", + "band" + ], + "type": "string" + }, + "SchemeParams": { + "additionalProperties": false, + "properties": { + "extent": { + "description": "For sequential and diverging schemes only, determines the extent of the color range to use. For example `[0.2, 1]` will rescale the color scheme such that color values in the range _[0, 0.2)_ are excluded from the scheme.", + "items": { + "type": "number" + }, + "type": "array" + }, + "name": { + "description": "A color scheme name for sequential/ordinal scales (e.g., `\"category10\"` or `\"viridis\"`).\n\nFor the full list of supported scheme, please refer to the [Vega Scheme](https://vega.github.io/vega/docs/schemes/#reference) reference.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "SelectionConfig": { + "additionalProperties": false, + "properties": { + "interval": { + "$ref": "#/definitions/IntervalSelectionConfig", + "description": "The default definition for an [`interval`](selection.html#type) selection. All properties and transformations\nfor an interval selection definition (except `type`) may be specified here.\n\nFor instance, setting `interval` to `{\"translate\": false}` disables the ability to move\ninterval selections by default." + }, + "multi": { + "$ref": "#/definitions/MultiSelectionConfig", + "description": "The default definition for a [`multi`](selection.html#type) selection. All properties and transformations\nfor a multi selection definition (except `type`) may be specified here.\n\nFor instance, setting `multi` to `{\"toggle\": \"event.altKey\"}` adds additional values to\nmulti selections when clicking with the alt-key pressed by default." + }, + "single": { + "$ref": "#/definitions/SingleSelectionConfig", + "description": "The default definition for a [`single`](selection.html#type) selection. All properties and transformations\n for a single selection definition (except `type`) may be specified here.\n\nFor instance, setting `single` to `{\"on\": \"dblclick\"}` populates single selections on double-click by default." + } + }, + "type": "object" + }, + "SelectionDef": { + "anyOf": [ + { + "$ref": "#/definitions/SingleSelection" + }, + { + "$ref": "#/definitions/MultiSelection" + }, + { + "$ref": "#/definitions/IntervalSelection" + } + ] + }, + "SelectionDomain": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "field": { + "description": "The field name to extract selected values for, when a selection is [projected](project.html)\nover multiple fields or encodings.", + "type": "string" + }, + "selection": { + "description": "The name of a selection.", + "type": "string" + } + }, + "required": [ + "selection" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "encoding": { + "description": "The encoding channel to extract selected values for, when a selection is [projected](project.html)\nover multiple fields or encodings.", + "type": "string" + }, + "selection": { + "description": "The name of a selection.", + "type": "string" + } + }, + "required": [ + "selection" + ], + "type": "object" + } + ] + }, + "SelectionFilter": { + "additionalProperties": false, + "properties": { + "selection": { + "$ref": "#/definitions/SelectionOperand", + "description": "Filter using a selection name." + } + }, + "required": [ + "selection" + ], + "type": "object" + }, + "SelectionResolution": { + "enum": [ + "global", + "union", + "intersect" + ], + "type": "string" + }, + "SingleDefChannel": { + "enum": [ + "x", + "y", + "x2", + "y2", + "row", + "column", + "size", + "shape", + "color", + "opacity", + "text", + "tooltip" + ], + "type": "string" + }, + "SingleSelection": { + "additionalProperties": false, + "properties": { + "bind": { + "anyOf": [ + { + "$ref": "#/definitions/VgBinding" + }, + { + "additionalProperties": { + "$ref": "#/definitions/VgBinding" + }, + "type": "object" + } + ], + "description": "Establish a two-way binding between a single selection and input elements\n(also known as dynamic query widgets). A binding takes the form of\nVega's [input element binding definition](https://vega.github.io/vega/docs/signals/#bind)\nor can be a mapping between projected field/encodings and binding definitions.\n\nSee the [bind transform](bind.html) documentation for more information." + }, + "empty": { + "description": "By default, all data values are considered to lie within an empty selection.\nWhen set to `none`, empty selections contain no data values.", + "enum": [ + "all", + "none" + ], + "type": "string" + }, + "encodings": { + "description": "An array of encoding channels. The corresponding data field values\nmust match for a data tuple to fall within the selection.", + "items": { + "$ref": "#/definitions/SingleDefChannel" + }, + "type": "array" + }, + "fields": { + "description": "An array of field names whose values must match for a data tuple to\nfall within the selection.", + "items": { + "type": "string" + }, + "type": "array" + }, + "nearest": { + "description": "When true, an invisible voronoi diagram is computed to accelerate discrete\nselection. The data value _nearest_ the mouse cursor is added to the selection.\n\nSee the [nearest transform](nearest.html) documentation for more information.", + "type": "boolean" + }, + "on": { + "$ref": "#/definitions/VgEventStream", + "description": "A [Vega event stream](https://vega.github.io/vega/docs/event-streams/) (object or selector) that triggers the selection.\nFor interval selections, the event stream must specify a [start and end](https://vega.github.io/vega/docs/event-streams/#between-filters)." + }, + "resolve": { + "$ref": "#/definitions/SelectionResolution", + "description": "With layered and multi-view displays, a strategy that determines how\nselections' data queries are resolved when applied in a filter transform,\nconditional encoding rule, or scale domain." + }, + "type": { + "enum": [ + "single" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "SingleSelectionConfig": { + "additionalProperties": false, + "properties": { + "bind": { + "anyOf": [ + { + "$ref": "#/definitions/VgBinding" + }, + { + "additionalProperties": { + "$ref": "#/definitions/VgBinding" + }, + "type": "object" + } + ], + "description": "Establish a two-way binding between a single selection and input elements\n(also known as dynamic query widgets). A binding takes the form of\nVega's [input element binding definition](https://vega.github.io/vega/docs/signals/#bind)\nor can be a mapping between projected field/encodings and binding definitions.\n\nSee the [bind transform](bind.html) documentation for more information." + }, + "empty": { + "description": "By default, all data values are considered to lie within an empty selection.\nWhen set to `none`, empty selections contain no data values.", + "enum": [ + "all", + "none" + ], + "type": "string" + }, + "encodings": { + "description": "An array of encoding channels. The corresponding data field values\nmust match for a data tuple to fall within the selection.", + "items": { + "$ref": "#/definitions/SingleDefChannel" + }, + "type": "array" + }, + "fields": { + "description": "An array of field names whose values must match for a data tuple to\nfall within the selection.", + "items": { + "type": "string" + }, + "type": "array" + }, + "nearest": { + "description": "When true, an invisible voronoi diagram is computed to accelerate discrete\nselection. The data value _nearest_ the mouse cursor is added to the selection.\n\nSee the [nearest transform](nearest.html) documentation for more information.", + "type": "boolean" + }, + "on": { + "$ref": "#/definitions/VgEventStream", + "description": "A [Vega event stream](https://vega.github.io/vega/docs/event-streams/) (object or selector) that triggers the selection.\nFor interval selections, the event stream must specify a [start and end](https://vega.github.io/vega/docs/event-streams/#between-filters)." + }, + "resolve": { + "$ref": "#/definitions/SelectionResolution", + "description": "With layered and multi-view displays, a strategy that determines how\nselections' data queries are resolved when applied in a filter transform,\nconditional encoding rule, or scale domain." + } + }, + "type": "object" + }, + "SingleTimeUnit": { + "anyOf": [ + { + "$ref": "#/definitions/LocalSingleTimeUnit" + }, + { + "$ref": "#/definitions/UtcSingleTimeUnit" + } + ] + }, + "SortField": { + "additionalProperties": false, + "properties": { + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RepeatRef" + } + ], + "description": "The data [field](field.html) to sort by.\n\n__Default value:__ If unspecified, defaults to the field specified in the outer data reference." + }, + "op": { + "$ref": "#/definitions/AggregateOp", + "description": "An [aggregate operation](aggregate.html#ops) to perform on the field prior to sorting (e.g., `\"count\"`, `\"mean\"` and `\"median\"`).\nThis property is required in cases where the sort field and the data reference field do not match.\nThe input data objects will be aggregated, grouped by the encoded data field.\n\nFor a full list of operations, please see the documentation for [aggregate](aggregate.html#ops)." + }, + "order": { + "$ref": "#/definitions/SortOrder", + "description": "The sort order. One of `\"ascending\"` (default) or `\"descending\"`." + } + }, + "required": [ + "op" + ], + "type": "object" + }, + "SortOrder": { + "enum": [ + "ascending", + "descending", + null + ], + "type": [ + "string", + "null" + ] + }, + "StackOffset": { + "enum": [ + "zero", + "center", + "normalize" + ], + "type": "string" + }, + "StyleConfigIndex": { + "additionalProperties": { + "$ref": "#/definitions/VgMarkConfig" + }, + "type": "object" + }, + "TextConfig": { + "additionalProperties": false, + "properties": { + "align": { + "$ref": "#/definitions/HorizontalAlign", + "description": "The horizontal alignment of the text. One of `\"left\"`, `\"right\"`, `\"center\"`." + }, + "angle": { + "description": "The rotation angle of the text, in degrees.", + "maximum": 360, + "minimum": 0, + "type": "number" + }, + "baseline": { + "$ref": "#/definitions/VerticalAlign", + "description": "The vertical alignment of the text. One of `\"top\"`, `\"middle\"`, `\"bottom\"`.\n\n__Default value:__ `\"middle\"`" + }, + "color": { + "description": "Default color. Note that `fill` and `stroke` have higher precedence than `color` and will override `color`.\n\n__Default value:__ `\"#4682b4\"`\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "string" + }, + "dx": { + "description": "The horizontal offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "dy": { + "description": "The vertical offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "fill": { + "description": "Default Fill Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "fillOpacity": { + "description": "The fill opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "filled": { + "description": "Whether the mark's color should be used as fill color instead of stroke color.\n\n__Default value:__ `true` for all marks except `point` and `false` for `point`.\n\n__Applicable for:__ `bar`, `point`, `circle`, `square`, and `area` marks.\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "boolean" + }, + "font": { + "description": "The typeface to set the text in (e.g., `\"Helvetica Neue\"`).", + "type": "string" + }, + "fontSize": { + "description": "The font size, in pixels.", + "minimum": 0, + "type": "number" + }, + "fontStyle": { + "$ref": "#/definitions/FontStyle", + "description": "The font style (e.g., `\"italic\"`)." + }, + "fontWeight": { + "anyOf": [ + { + "$ref": "#/definitions/FontWeight" + }, + { + "$ref": "#/definitions/FontWeightNumber" + } + ], + "description": "The font weight (e.g., `\"bold\"`)." + }, + "interpolate": { + "$ref": "#/definitions/Interpolate", + "description": "The line interpolation method to use for line and area marks. One of the following:\n- `\"linear\"`: piecewise linear segments, as in a polyline.\n- `\"linear-closed\"`: close the linear segments to form a polygon.\n- `\"step\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"step-before\"`: alternate between vertical and horizontal segments, as in a step function.\n- `\"step-after\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"basis\"`: a B-spline, with control point duplication on the ends.\n- `\"basis-open\"`: an open B-spline; may not intersect the start or end.\n- `\"basis-closed\"`: a closed B-spline, as in a loop.\n- `\"cardinal\"`: a Cardinal spline, with control point duplication on the ends.\n- `\"cardinal-open\"`: an open Cardinal spline; may not intersect the start or end, but will intersect other control points.\n- `\"cardinal-closed\"`: a closed Cardinal spline, as in a loop.\n- `\"bundle\"`: equivalent to basis, except the tension parameter is used to straighten the spline.\n- `\"monotone\"`: cubic interpolation that preserves monotonicity in y." + }, + "limit": { + "description": "The maximum length of the text mark in pixels (default 0, indicating no limit). The text value will be automatically truncated if the rendered size exceeds the limit.", + "type": "number" + }, + "opacity": { + "description": "The overall opacity (value between [0,1]).\n\n__Default value:__ `0.7` for non-aggregate plots with `point`, `tick`, `circle`, or `square` marks or layered `bar` charts and `1` otherwise.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "orient": { + "$ref": "#/definitions/Orient", + "description": "The orientation of a non-stacked bar, tick, area, and line charts.\nThe value is either horizontal (default) or vertical.\n- For bar, rule and tick, this determines whether the size of the bar and tick\nshould be applied to x or y dimension.\n- For area, this property determines the orient property of the Vega output.\n- For line, this property determines the sort order of the points in the line\nif `config.sortLineBy` is not specified.\nFor stacked charts, this is always determined by the orientation of the stack;\ntherefore explicitly specified value will be ignored." + }, + "radius": { + "description": "Polar coordinate radial offset, in pixels, of the text label from the origin determined by the `x` and `y` properties.", + "minimum": 0, + "type": "number" + }, + "shape": { + "description": "The default symbol shape to use. One of: `\"circle\"` (default), `\"square\"`, `\"cross\"`, `\"diamond\"`, `\"triangle-up\"`, or `\"triangle-down\"`, or a custom SVG path.\n\n__Default value:__ `\"circle\"`", + "type": "string" + }, + "shortTimeLabels": { + "description": "Whether month names and weekday names should be abbreviated.", + "type": "boolean" + }, + "size": { + "description": "The pixel area each the point/circle/square.\nFor example: in the case of circles, the radius is determined in part by the square root of the size value.\n\n__Default value:__ `30`", + "minimum": 0, + "type": "number" + }, + "stroke": { + "description": "Default Stroke Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "strokeDash": { + "description": "An array of alternating stroke, space lengths for creating dashed or dotted lines.", + "items": { + "type": "number" + }, + "type": "array" + }, + "strokeDashOffset": { + "description": "The offset (in pixels) into which to begin drawing with the stroke dash array.", + "type": "number" + }, + "strokeOpacity": { + "description": "The stroke opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "strokeWidth": { + "description": "The stroke width, in pixels.", + "minimum": 0, + "type": "number" + }, + "tension": { + "description": "Depending on the interpolation type, sets the tension parameter (for line and area marks).", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "text": { + "description": "Placeholder text if the `text` channel is not specified", + "type": "string" + }, + "theta": { + "description": "Polar coordinate angle, in radians, of the text label from the origin determined by the `x` and `y` properties. Values for `theta` follow the same convention of `arc` mark `startAngle` and `endAngle` properties: angles are measured in radians, with `0` indicating \"north\".", + "type": "number" + } + }, + "type": "object" + }, + "TickConfig": { + "additionalProperties": false, + "properties": { + "align": { + "$ref": "#/definitions/HorizontalAlign", + "description": "The horizontal alignment of the text. One of `\"left\"`, `\"right\"`, `\"center\"`." + }, + "angle": { + "description": "The rotation angle of the text, in degrees.", + "maximum": 360, + "minimum": 0, + "type": "number" + }, + "bandSize": { + "description": "The width of the ticks.\n\n__Default value:__ 2/3 of rangeStep.", + "minimum": 0, + "type": "number" + }, + "baseline": { + "$ref": "#/definitions/VerticalAlign", + "description": "The vertical alignment of the text. One of `\"top\"`, `\"middle\"`, `\"bottom\"`.\n\n__Default value:__ `\"middle\"`" + }, + "color": { + "description": "Default color. Note that `fill` and `stroke` have higher precedence than `color` and will override `color`.\n\n__Default value:__ `\"#4682b4\"`\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "string" + }, + "dx": { + "description": "The horizontal offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "dy": { + "description": "The vertical offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "fill": { + "description": "Default Fill Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "fillOpacity": { + "description": "The fill opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "filled": { + "description": "Whether the mark's color should be used as fill color instead of stroke color.\n\n__Default value:__ `true` for all marks except `point` and `false` for `point`.\n\n__Applicable for:__ `bar`, `point`, `circle`, `square`, and `area` marks.\n\n__Note:__ This property cannot be used in a [style config](mark.html#style-config).", + "type": "boolean" + }, + "font": { + "description": "The typeface to set the text in (e.g., `\"Helvetica Neue\"`).", + "type": "string" + }, + "fontSize": { + "description": "The font size, in pixels.", + "minimum": 0, + "type": "number" + }, + "fontStyle": { + "$ref": "#/definitions/FontStyle", + "description": "The font style (e.g., `\"italic\"`)." + }, + "fontWeight": { + "anyOf": [ + { + "$ref": "#/definitions/FontWeight" + }, + { + "$ref": "#/definitions/FontWeightNumber" + } + ], + "description": "The font weight (e.g., `\"bold\"`)." + }, + "interpolate": { + "$ref": "#/definitions/Interpolate", + "description": "The line interpolation method to use for line and area marks. One of the following:\n- `\"linear\"`: piecewise linear segments, as in a polyline.\n- `\"linear-closed\"`: close the linear segments to form a polygon.\n- `\"step\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"step-before\"`: alternate between vertical and horizontal segments, as in a step function.\n- `\"step-after\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"basis\"`: a B-spline, with control point duplication on the ends.\n- `\"basis-open\"`: an open B-spline; may not intersect the start or end.\n- `\"basis-closed\"`: a closed B-spline, as in a loop.\n- `\"cardinal\"`: a Cardinal spline, with control point duplication on the ends.\n- `\"cardinal-open\"`: an open Cardinal spline; may not intersect the start or end, but will intersect other control points.\n- `\"cardinal-closed\"`: a closed Cardinal spline, as in a loop.\n- `\"bundle\"`: equivalent to basis, except the tension parameter is used to straighten the spline.\n- `\"monotone\"`: cubic interpolation that preserves monotonicity in y." + }, + "limit": { + "description": "The maximum length of the text mark in pixels (default 0, indicating no limit). The text value will be automatically truncated if the rendered size exceeds the limit.", + "type": "number" + }, + "opacity": { + "description": "The overall opacity (value between [0,1]).\n\n__Default value:__ `0.7` for non-aggregate plots with `point`, `tick`, `circle`, or `square` marks or layered `bar` charts and `1` otherwise.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "orient": { + "$ref": "#/definitions/Orient", + "description": "The orientation of a non-stacked bar, tick, area, and line charts.\nThe value is either horizontal (default) or vertical.\n- For bar, rule and tick, this determines whether the size of the bar and tick\nshould be applied to x or y dimension.\n- For area, this property determines the orient property of the Vega output.\n- For line, this property determines the sort order of the points in the line\nif `config.sortLineBy` is not specified.\nFor stacked charts, this is always determined by the orientation of the stack;\ntherefore explicitly specified value will be ignored." + }, + "radius": { + "description": "Polar coordinate radial offset, in pixels, of the text label from the origin determined by the `x` and `y` properties.", + "minimum": 0, + "type": "number" + }, + "shape": { + "description": "The default symbol shape to use. One of: `\"circle\"` (default), `\"square\"`, `\"cross\"`, `\"diamond\"`, `\"triangle-up\"`, or `\"triangle-down\"`, or a custom SVG path.\n\n__Default value:__ `\"circle\"`", + "type": "string" + }, + "size": { + "description": "The pixel area each the point/circle/square.\nFor example: in the case of circles, the radius is determined in part by the square root of the size value.\n\n__Default value:__ `30`", + "minimum": 0, + "type": "number" + }, + "stroke": { + "description": "Default Stroke Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "strokeDash": { + "description": "An array of alternating stroke, space lengths for creating dashed or dotted lines.", + "items": { + "type": "number" + }, + "type": "array" + }, + "strokeDashOffset": { + "description": "The offset (in pixels) into which to begin drawing with the stroke dash array.", + "type": "number" + }, + "strokeOpacity": { + "description": "The stroke opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "strokeWidth": { + "description": "The stroke width, in pixels.", + "minimum": 0, + "type": "number" + }, + "tension": { + "description": "Depending on the interpolation type, sets the tension parameter (for line and area marks).", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "text": { + "description": "Placeholder text if the `text` channel is not specified", + "type": "string" + }, + "theta": { + "description": "Polar coordinate angle, in radians, of the text label from the origin determined by the `x` and `y` properties. Values for `theta` follow the same convention of `arc` mark `startAngle` and `endAngle` properties: angles are measured in radians, with `0` indicating \"north\".", + "type": "number" + }, + "thickness": { + "description": "Thickness of the tick mark.\n\n__Default value:__ `1`", + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "TimeUnit": { + "anyOf": [ + { + "$ref": "#/definitions/SingleTimeUnit" + }, + { + "$ref": "#/definitions/MultiTimeUnit" + } + ] + }, + "TimeUnitTransform": { + "additionalProperties": false, + "properties": { + "as": { + "description": "The output field to write the timeUnit value.", + "type": "string" + }, + "field": { + "description": "The data field to apply time unit.", + "type": "string" + }, + "timeUnit": { + "$ref": "#/definitions/TimeUnit", + "description": "The timeUnit." + } + }, + "required": [ + "timeUnit", + "field", + "as" + ], + "type": "object" + }, + "TitleOrient": { + "enum": [ + "top", + "bottom", + "left", + "right" + ], + "type": "string" + }, + "TitleParams": { + "additionalProperties": false, + "properties": { + "anchor": { + "$ref": "#/definitions/Anchor", + "description": "The anchor position for placing the title. One of `\"start\"`, `\"middle\"`, or `\"end\"`. For example, with an orientation of top these anchor positions map to a left-, center-, or right-aligned title.\n\n__Default value:__ `\"middle\"` for [single](spec.html) and [layered](layer.html) views.\n`\"start\"` for other composite views.\n\n__Note:__ [For now](https://github.com/vega/vega-lite/issues/2875), `anchor` is only customizable only for [single](spec.html) and [layered](layer.html) views. For other composite views, `anchor` is always `\"start\"`." + }, + "offset": { + "description": "The orthogonal offset in pixels by which to displace the title from its position along the edge of the chart.", + "type": "number" + }, + "orient": { + "$ref": "#/definitions/TitleOrient", + "description": "The orientation of the title relative to the chart. One of `\"top\"` (the default), `\"bottom\"`, `\"left\"`, or `\"right\"`." + }, + "style": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "A [mark style property](config.html#style) to apply to the title text mark.\n\n__Default value:__ `\"group-title\"`." + }, + "text": { + "description": "The title text.", + "type": "string" + } + }, + "required": [ + "text" + ], + "type": "object" + }, + "TopLevel": { + "additionalProperties": false, + "properties": { + "$schema": { + "description": "URL to [JSON schema](http://json-schema.org/) for a Vega-Lite specification. Unless you have a reason to change this, use `https://vega.github.io/schema/vega-lite/v2.json`. Setting the `$schema` property allows automatic validation and autocomplete in editors that support JSON schema.", + "format": "uri", + "type": "string" + }, + "autosize": { + "anyOf": [ + { + "$ref": "#/definitions/AutosizeType" + }, + { + "$ref": "#/definitions/AutoSizeParams" + } + ], + "description": "Sets how the visualization size should be determined. If a string, should be one of `\"pad\"`, `\"fit\"` or `\"none\"`.\nObject values can additionally specify parameters for content sizing and automatic resizing.\n`\"fit\"` is only supported for single and layered views that don't use `rangeStep`.\n\n__Default value__: `pad`" + }, + "background": { + "description": "CSS color property to use as the background of visualization.\n\n__Default value:__ none (transparent)", + "type": "string" + }, + "config": { + "$ref": "#/definitions/Config", + "description": "Vega-Lite configuration object. This property can only be defined at the top-level of a specification." + }, + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "encoding": { + "$ref": "#/definitions/EncodingWithFacet", + "description": "A key-value mapping between encoding channels and definition of fields." + }, + "height": { + "description": "The height of a visualization.\n\n__Default value:__\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its y-channel has a [continuous scale](scale.html#continuous), the height will be the value of [`config.view.height`](spec.html#config).\n- For y-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the height is [determined by the range step, paddings, and the cardinality of the field mapped to y-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the height will be the value of [`config.view.height`](spec.html#config).\n- If no field is mapped to `y` channel, the `height` will be the value of `rangeStep`.\n\n__Note__: For plots with [`row` and `column` channels](encoding.html#facet), this represents the height of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + }, + "mark": { + "$ref": "#/definitions/AnyMark", + "description": "A string describing the mark type (one of `\"bar\"`, `\"circle\"`, `\"square\"`, `\"tick\"`, `\"line\"`,\n`\"area\"`, `\"point\"`, `\"rule\"`, and `\"text\"`) or a [mark definition object](mark.html#mark-def)." + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "padding": { + "$ref": "#/definitions/Padding", + "description": "The default visualization padding, in pixels, from the edge of the visualization canvas to the data rectangle. If a number, specifies padding for all sides.\nIf an object, the value should have the format `{\"left\": 5, \"top\": 5, \"right\": 5, \"bottom\": 5}` to specify padding for each side of the visualization.\n\n__Default value__: `5`" + }, + "selection": { + "additionalProperties": { + "$ref": "#/definitions/SelectionDef" + }, + "description": "A key-value mapping between selection names and definitions.", + "type": "object" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + }, + "width": { + "description": "The width of a visualization.\n\n__Default value:__ This will be determined by the following rules:\n\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its x-channel has a [continuous scale](scale.html#continuous), the width will be the value of [`config.view.width`](spec.html#config).\n- For x-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the width is [determined by the range step, paddings, and the cardinality of the field mapped to x-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the width will be the value of [`config.view.width`](spec.html#config).\n- If no field is mapped to `x` channel, the `width` will be the value of [`config.scale.textXRangeStep`](size.html#default-width-and-height) for `text` mark and the value of `rangeStep` for other marks.\n\n__Note:__ For plots with [`row` and `column` channels](encoding.html#facet), this represents the width of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + } + }, + "required": [ + "encoding", + "mark" + ], + "type": "object" + }, + "TopLevel": { + "additionalProperties": false, + "properties": { + "$schema": { + "description": "URL to [JSON schema](http://json-schema.org/) for a Vega-Lite specification. Unless you have a reason to change this, use `https://vega.github.io/schema/vega-lite/v2.json`. Setting the `$schema` property allows automatic validation and autocomplete in editors that support JSON schema.", + "format": "uri", + "type": "string" + }, + "autosize": { + "anyOf": [ + { + "$ref": "#/definitions/AutosizeType" + }, + { + "$ref": "#/definitions/AutoSizeParams" + } + ], + "description": "Sets how the visualization size should be determined. If a string, should be one of `\"pad\"`, `\"fit\"` or `\"none\"`.\nObject values can additionally specify parameters for content sizing and automatic resizing.\n`\"fit\"` is only supported for single and layered views that don't use `rangeStep`.\n\n__Default value__: `pad`" + }, + "background": { + "description": "CSS color property to use as the background of visualization.\n\n__Default value:__ none (transparent)", + "type": "string" + }, + "config": { + "$ref": "#/definitions/Config", + "description": "Vega-Lite configuration object. This property can only be defined at the top-level of a specification." + }, + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "facet": { + "$ref": "#/definitions/FacetMapping", + "description": "An object that describes mappings between `row` and `column` channels and their field definitions." + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "padding": { + "$ref": "#/definitions/Padding", + "description": "The default visualization padding, in pixels, from the edge of the visualization canvas to the data rectangle. If a number, specifies padding for all sides.\nIf an object, the value should have the format `{\"left\": 5, \"top\": 5, \"right\": 5, \"bottom\": 5}` to specify padding for each side of the visualization.\n\n__Default value__: `5`" + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale, axis, and legend resolutions for facets." + }, + "spec": { + "anyOf": [ + { + "$ref": "#/definitions/LayerSpec" + }, + { + "$ref": "#/definitions/CompositeUnitSpec" + } + ], + "description": "A specification of the view that gets faceted." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + } + }, + "required": [ + "facet", + "spec" + ], + "type": "object" + }, + "TopLevel": { + "additionalProperties": false, + "properties": { + "$schema": { + "description": "URL to [JSON schema](http://json-schema.org/) for a Vega-Lite specification. Unless you have a reason to change this, use `https://vega.github.io/schema/vega-lite/v2.json`. Setting the `$schema` property allows automatic validation and autocomplete in editors that support JSON schema.", + "format": "uri", + "type": "string" + }, + "autosize": { + "anyOf": [ + { + "$ref": "#/definitions/AutosizeType" + }, + { + "$ref": "#/definitions/AutoSizeParams" + } + ], + "description": "Sets how the visualization size should be determined. If a string, should be one of `\"pad\"`, `\"fit\"` or `\"none\"`.\nObject values can additionally specify parameters for content sizing and automatic resizing.\n`\"fit\"` is only supported for single and layered views that don't use `rangeStep`.\n\n__Default value__: `pad`" + }, + "background": { + "description": "CSS color property to use as the background of visualization.\n\n__Default value:__ none (transparent)", + "type": "string" + }, + "config": { + "$ref": "#/definitions/Config", + "description": "Vega-Lite configuration object. This property can only be defined at the top-level of a specification." + }, + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "hconcat": { + "description": "A list of views that should be concatenated and put into a row.", + "items": { + "$ref": "#/definitions/Spec" + }, + "type": "array" + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "padding": { + "$ref": "#/definitions/Padding", + "description": "The default visualization padding, in pixels, from the edge of the visualization canvas to the data rectangle. If a number, specifies padding for all sides.\nIf an object, the value should have the format `{\"left\": 5, \"top\": 5, \"right\": 5, \"bottom\": 5}` to specify padding for each side of the visualization.\n\n__Default value__: `5`" + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale, axis, and legend resolutions for horizontally concatenated charts." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + } + }, + "required": [ + "hconcat" + ], + "type": "object" + }, + "TopLevel": { + "additionalProperties": false, + "properties": { + "$schema": { + "description": "URL to [JSON schema](http://json-schema.org/) for a Vega-Lite specification. Unless you have a reason to change this, use `https://vega.github.io/schema/vega-lite/v2.json`. Setting the `$schema` property allows automatic validation and autocomplete in editors that support JSON schema.", + "format": "uri", + "type": "string" + }, + "autosize": { + "anyOf": [ + { + "$ref": "#/definitions/AutosizeType" + }, + { + "$ref": "#/definitions/AutoSizeParams" + } + ], + "description": "Sets how the visualization size should be determined. If a string, should be one of `\"pad\"`, `\"fit\"` or `\"none\"`.\nObject values can additionally specify parameters for content sizing and automatic resizing.\n`\"fit\"` is only supported for single and layered views that don't use `rangeStep`.\n\n__Default value__: `pad`" + }, + "background": { + "description": "CSS color property to use as the background of visualization.\n\n__Default value:__ none (transparent)", + "type": "string" + }, + "config": { + "$ref": "#/definitions/Config", + "description": "Vega-Lite configuration object. This property can only be defined at the top-level of a specification." + }, + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "height": { + "description": "The height of a visualization.\n\n__Default value:__\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its y-channel has a [continuous scale](scale.html#continuous), the height will be the value of [`config.view.height`](spec.html#config).\n- For y-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the height is [determined by the range step, paddings, and the cardinality of the field mapped to y-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the height will be the value of [`config.view.height`](spec.html#config).\n- If no field is mapped to `y` channel, the `height` will be the value of `rangeStep`.\n\n__Note__: For plots with [`row` and `column` channels](encoding.html#facet), this represents the height of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + }, + "layer": { + "description": "Layer or single view specifications to be layered.\n\n__Note__: Specifications inside `layer` cannot use `row` and `column` channels as layering facet specifications is not allowed.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/LayerSpec" + }, + { + "$ref": "#/definitions/CompositeUnitSpec" + } + ] + }, + "type": "array" + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "padding": { + "$ref": "#/definitions/Padding", + "description": "The default visualization padding, in pixels, from the edge of the visualization canvas to the data rectangle. If a number, specifies padding for all sides.\nIf an object, the value should have the format `{\"left\": 5, \"top\": 5, \"right\": 5, \"bottom\": 5}` to specify padding for each side of the visualization.\n\n__Default value__: `5`" + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale, axis, and legend resolutions for layers." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + }, + "width": { + "description": "The width of a visualization.\n\n__Default value:__ This will be determined by the following rules:\n\n- If a view's [`autosize`](size.html#autosize) type is `\"fit\"` or its x-channel has a [continuous scale](scale.html#continuous), the width will be the value of [`config.view.width`](spec.html#config).\n- For x-axis with a band or point scale: if [`rangeStep`](scale.html#band) is a numeric value or unspecified, the width is [determined by the range step, paddings, and the cardinality of the field mapped to x-channel](scale.html#band). Otherwise, if the `rangeStep` is `null`, the width will be the value of [`config.view.width`](spec.html#config).\n- If no field is mapped to `x` channel, the `width` will be the value of [`config.scale.textXRangeStep`](size.html#default-width-and-height) for `text` mark and the value of `rangeStep` for other marks.\n\n__Note:__ For plots with [`row` and `column` channels](encoding.html#facet), this represents the width of a single view.\n\n__See also:__ The documentation for [width and height](size.html) contains more examples.", + "type": "number" + } + }, + "required": [ + "layer" + ], + "type": "object" + }, + "TopLevel": { + "additionalProperties": false, + "properties": { + "$schema": { + "description": "URL to [JSON schema](http://json-schema.org/) for a Vega-Lite specification. Unless you have a reason to change this, use `https://vega.github.io/schema/vega-lite/v2.json`. Setting the `$schema` property allows automatic validation and autocomplete in editors that support JSON schema.", + "format": "uri", + "type": "string" + }, + "autosize": { + "anyOf": [ + { + "$ref": "#/definitions/AutosizeType" + }, + { + "$ref": "#/definitions/AutoSizeParams" + } + ], + "description": "Sets how the visualization size should be determined. If a string, should be one of `\"pad\"`, `\"fit\"` or `\"none\"`.\nObject values can additionally specify parameters for content sizing and automatic resizing.\n`\"fit\"` is only supported for single and layered views that don't use `rangeStep`.\n\n__Default value__: `pad`" + }, + "background": { + "description": "CSS color property to use as the background of visualization.\n\n__Default value:__ none (transparent)", + "type": "string" + }, + "config": { + "$ref": "#/definitions/Config", + "description": "Vega-Lite configuration object. This property can only be defined at the top-level of a specification." + }, + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "padding": { + "$ref": "#/definitions/Padding", + "description": "The default visualization padding, in pixels, from the edge of the visualization canvas to the data rectangle. If a number, specifies padding for all sides.\nIf an object, the value should have the format `{\"left\": 5, \"top\": 5, \"right\": 5, \"bottom\": 5}` to specify padding for each side of the visualization.\n\n__Default value__: `5`" + }, + "repeat": { + "$ref": "#/definitions/Repeat", + "description": "An object that describes what fields should be repeated into views that are laid out as a `row` or `column`." + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale and legend resolutions for repeated charts." + }, + "spec": { + "$ref": "#/definitions/Spec" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + } + }, + "required": [ + "repeat", + "spec" + ], + "type": "object" + }, + "TopLevel": { + "additionalProperties": false, + "properties": { + "$schema": { + "description": "URL to [JSON schema](http://json-schema.org/) for a Vega-Lite specification. Unless you have a reason to change this, use `https://vega.github.io/schema/vega-lite/v2.json`. Setting the `$schema` property allows automatic validation and autocomplete in editors that support JSON schema.", + "format": "uri", + "type": "string" + }, + "autosize": { + "anyOf": [ + { + "$ref": "#/definitions/AutosizeType" + }, + { + "$ref": "#/definitions/AutoSizeParams" + } + ], + "description": "Sets how the visualization size should be determined. If a string, should be one of `\"pad\"`, `\"fit\"` or `\"none\"`.\nObject values can additionally specify parameters for content sizing and automatic resizing.\n`\"fit\"` is only supported for single and layered views that don't use `rangeStep`.\n\n__Default value__: `pad`" + }, + "background": { + "description": "CSS color property to use as the background of visualization.\n\n__Default value:__ none (transparent)", + "type": "string" + }, + "config": { + "$ref": "#/definitions/Config", + "description": "Vega-Lite configuration object. This property can only be defined at the top-level of a specification." + }, + "data": { + "$ref": "#/definitions/Data", + "description": "An object describing the data source" + }, + "description": { + "description": "Description of this mark for commenting purpose.", + "type": "string" + }, + "name": { + "description": "Name of the visualization for later reference.", + "type": "string" + }, + "padding": { + "$ref": "#/definitions/Padding", + "description": "The default visualization padding, in pixels, from the edge of the visualization canvas to the data rectangle. If a number, specifies padding for all sides.\nIf an object, the value should have the format `{\"left\": 5, \"top\": 5, \"right\": 5, \"bottom\": 5}` to specify padding for each side of the visualization.\n\n__Default value__: `5`" + }, + "resolve": { + "$ref": "#/definitions/Resolve", + "description": "Scale, axis, and legend resolutions for vertically concatenated charts." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/TitleParams" + } + ], + "description": "Title for the plot." + }, + "transform": { + "description": "An array of data transformations such as filter and new field calculation.", + "items": { + "$ref": "#/definitions/Transform" + }, + "type": "array" + }, + "vconcat": { + "description": "A list of views that should be concatenated and put into a column.", + "items": { + "$ref": "#/definitions/Spec" + }, + "type": "array" + } + }, + "required": [ + "vconcat" + ], + "type": "object" + }, + "TopLevelExtendedSpec": { + "anyOf": [ + { + "$ref": "#/definitions/TopLevel" + }, + { + "$ref": "#/definitions/TopLevel" + }, + { + "$ref": "#/definitions/TopLevel" + }, + { + "$ref": "#/definitions/TopLevel" + }, + { + "$ref": "#/definitions/TopLevel" + }, + { + "$ref": "#/definitions/TopLevel" + } + ] + }, + "TopoDataFormat": { + "additionalProperties": false, + "properties": { + "feature": { + "description": "The name of the TopoJSON object set to convert to a GeoJSON feature collection.\nFor example, in a map of the world, there may be an object set named `\"countries\"`.\nUsing the feature property, we can extract this set and generate a GeoJSON feature object for each country.", + "type": "string" + }, + "mesh": { + "description": "The name of the TopoJSON object set to convert to mesh.\nSimilar to the `feature` option, `mesh` extracts a named TopoJSON object set.\n Unlike the `feature` option, the corresponding geo data is returned as a single, unified mesh instance, not as individual GeoJSON features.\nExtracting a mesh is useful for more efficiently drawing borders or other geographic elements that you do not need to associate with specific regions such as individual countries, states or counties.", + "type": "string" + }, + "parse": { + "anyOf": [ + { + "enum": [ + "auto" + ], + "type": "string" + }, + { + "type": "object" + } + ], + "description": "If set to auto (the default), perform automatic type inference to determine the desired data types.\nAlternatively, a parsing directive object can be provided for explicit data types. Each property of the object corresponds to a field name, and the value to the desired data type (one of `\"number\"`, `\"boolean\"` or `\"date\"`).\nFor example, `\"parse\": {\"modified_on\": \"date\"}` parses the `modified_on` field in each input record a Date value.\n\nFor `\"date\"`, we parse data based using Javascript's [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse).\nFor Specific date formats can be provided (e.g., `{foo: 'date:\"%m%d%Y\"'}`), using the [d3-time-format syntax](https://github.com/d3/d3-time-format#locale_format). UTC date format parsing is supported similarly (e.g., `{foo: 'utc:\"%m%d%Y\"'}`). See more about [UTC time](timeunit.html#utc)" + }, + "type": { + "description": "Type of input data: `\"json\"`, `\"csv\"`, `\"tsv\"`.\nThe default format type is determined by the extension of the file URL.\nIf no extension is detected, `\"json\"` will be used by default.", + "enum": [ + "topojson" + ], + "type": "string" + } + }, + "type": "object" + }, + "Transform": { + "anyOf": [ + { + "$ref": "#/definitions/FilterTransform" + }, + { + "$ref": "#/definitions/CalculateTransform" + }, + { + "$ref": "#/definitions/LookupTransform" + }, + { + "$ref": "#/definitions/BinTransform" + }, + { + "$ref": "#/definitions/TimeUnitTransform" + }, + { + "$ref": "#/definitions/AggregateTransform" + } + ] + }, + "Type": { + "description": "Constants and utilities for data type \n Data type based on level of measurement ", + "enum": [ + "quantitative", + "ordinal", + "temporal", + "nominal" + ], + "type": "string" + }, + "UrlData": { + "additionalProperties": false, + "properties": { + "format": { + "$ref": "#/definitions/DataFormat", + "description": "An object that specifies the format for parsing the data file." + }, + "url": { + "description": "An URL from which to load the data set. Use the `format.type` property\nto ensure the loaded data is correctly parsed.", + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + }, + "UtcMultiTimeUnit": { + "enum": [ + "utcyearquarter", + "utcyearquartermonth", + "utcyearmonth", + "utcyearmonthdate", + "utcyearmonthdatehours", + "utcyearmonthdatehoursminutes", + "utcyearmonthdatehoursminutesseconds", + "utcquartermonth", + "utcmonthdate", + "utchoursminutes", + "utchoursminutesseconds", + "utcminutesseconds", + "utcsecondsmilliseconds" + ], + "type": "string" + }, + "UtcSingleTimeUnit": { + "enum": [ + "utcyear", + "utcquarter", + "utcmonth", + "utcday", + "utcdate", + "utchours", + "utcminutes", + "utcseconds", + "utcmilliseconds" + ], + "type": "string" + }, + "ValueDef": { + "additionalProperties": false, + "description": "Definition object for a constant value of an encoding channel.", + "properties": { + "value": { + "description": "A constant value in visual domain (e.g., `\"red\"` / \"#0099ff\" for color, values between `0` to `1` for opacity).", + "type": [ + "number", + "string", + "boolean" + ] + } + }, + "required": [ + "value" + ], + "type": "object" + }, + "MarkPropValueDefWithCondition": { + "additionalProperties": false, + "description": "A ValueDef with Condition\n{\n condition: {field: ...} | {value: ...},\n value: ...,\n}", + "properties": { + "condition": { + "anyOf": [ + { + "$ref": "#/definitions/Conditional" + }, + { + "$ref": "#/definitions/Conditional" + }, + { + "items": { + "$ref": "#/definitions/Conditional" + }, + "type": "array" + } + ], + "description": "A field definition or one or more value definition(s) with a selection predicate." + }, + "value": { + "description": "A constant value in visual domain.", + "type": [ + "number", + "string", + "boolean" + ] + } + }, + "type": "object" + }, + "TextValueDefWithCondition": { + "additionalProperties": false, + "description": "A ValueDef with Condition\n{\n condition: {field: ...} | {value: ...},\n value: ...,\n}", + "properties": { + "condition": { + "anyOf": [ + { + "$ref": "#/definitions/Conditional" + }, + { + "$ref": "#/definitions/Conditional" + }, + { + "items": { + "$ref": "#/definitions/Conditional" + }, + "type": "array" + } + ], + "description": "A field definition or one or more value definition(s) with a selection predicate." + }, + "value": { + "description": "A constant value in visual domain.", + "type": [ + "number", + "string", + "boolean" + ] + } + }, + "type": "object" + }, + "VerticalAlign": { + "enum": [ + "top", + "middle", + "bottom" + ], + "type": "string" + }, + "VgAxisConfig": { + "additionalProperties": false, + "properties": { + "bandPosition": { + "description": "An interpolation fraction indicating where, for `band` scales, axis ticks should be positioned. A value of `0` places ticks at the left edge of their bands. A value of `0.5` places ticks in the middle of their bands.", + "type": "number" + }, + "domain": { + "description": "A boolean flag indicating if the domain (the axis baseline) should be included as part of the axis.\n\n__Default value:__ `true`", + "type": "boolean" + }, + "domainColor": { + "description": "Color of axis domain line.\n\n__Default value:__ (none, using Vega default).", + "type": "string" + }, + "domainWidth": { + "description": "Stroke width of axis domain line\n\n__Default value:__ (none, using Vega default).", + "type": "number" + }, + "grid": { + "description": "A boolean flag indicating if grid lines should be included as part of the axis\n\n__Default value:__ `true` for [continuous scales](scale.html#continuous) that are not binned; otherwise, `false`.", + "type": "boolean" + }, + "gridColor": { + "description": "Color of gridlines.", + "type": "string" + }, + "gridDash": { + "description": "The offset (in pixels) into which to begin drawing with the grid dash array.", + "items": { + "type": "number" + }, + "type": "array" + }, + "gridOpacity": { + "description": "The stroke opacity of grid (value between [0,1])\n\n__Default value:__ (`1` by default)", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "gridWidth": { + "description": "The grid width, in pixels.", + "minimum": 0, + "type": "number" + }, + "labelAngle": { + "description": "The rotation angle of the axis labels.\n\n__Default value:__ `-90` for nominal and ordinal fields; `0` otherwise.", + "maximum": 360, + "minimum": -360, + "type": "number" + }, + "labelBound": { + "description": "Indicates if labels should be hidden if they exceed the axis range. If `false `(the default) no bounds overlap analysis is performed. If `true`, labels will be hidden if they exceed the axis range by more than 1 pixel. If this property is a number, it specifies the pixel tolerance: the maximum amount by which a label bounding box may exceed the axis range.\n\n__Default value:__ `false`.", + "type": [ + "boolean", + "number" + ] + }, + "labelColor": { + "description": "The color of the tick label, can be in hex color code or regular color name.", + "type": "string" + }, + "labelFlush": { + "description": "Indicates if the first and last axis labels should be aligned flush with the scale range. Flush alignment for a horizontal axis will left-align the first label and right-align the last label. For vertical axes, bottom and top text baselines are applied instead. If this property is a number, it also indicates the number of pixels by which to offset the first and last labels; for example, a value of 2 will flush-align the first and last labels and also push them 2 pixels outward from the center of the axis. The additional adjustment can sometimes help the labels better visually group with corresponding axis ticks.\n\n__Default value:__ `true` for axis of a continuous x-scale. Otherwise, `false`.", + "type": [ + "boolean", + "number" + ] + }, + "labelFont": { + "description": "The font of the tick label.", + "type": "string" + }, + "labelFontSize": { + "description": "The font size of the label, in pixels.", + "minimum": 0, + "type": "number" + }, + "labelLimit": { + "description": "Maximum allowed pixel width of axis tick labels.", + "type": "number" + }, + "labelOverlap": { + "anyOf": [ + { + "type": "boolean" + }, + { + "enum": [ + "parity" + ], + "type": "string" + }, + { + "enum": [ + "greedy" + ], + "type": "string" + } + ], + "description": "The strategy to use for resolving overlap of axis labels. If `false` (the default), no overlap reduction is attempted. If set to `true` or `\"parity\"`, a strategy of removing every other label is used (this works well for standard linear axes). If set to `\"greedy\"`, a linear scan of the labels is performed, removing any labels that overlaps with the last visible label (this often works better for log-scaled axes).\n\n__Default value:__ `true` for non-nominal fields with non-log scales; `\"greedy\"` for log scales; otherwise `false`." + }, + "labelPadding": { + "description": "The padding, in pixels, between axis and text labels.", + "type": "number" + }, + "labels": { + "description": "A boolean flag indicating if labels should be included as part of the axis.\n\n__Default value:__ `true`.", + "type": "boolean" + }, + "maxExtent": { + "description": "The maximum extent in pixels that axis ticks and labels should use. This determines a maximum offset value for axis titles.\n\n__Default value:__ `undefined`.", + "type": "number" + }, + "minExtent": { + "description": "The minimum extent in pixels that axis ticks and labels should use. This determines a minimum offset value for axis titles.\n\n__Default value:__ `30` for y-axis; `undefined` for x-axis.", + "type": "number" + }, + "tickColor": { + "description": "The color of the axis's tick.", + "type": "string" + }, + "tickRound": { + "description": "Boolean flag indicating if pixel position values should be rounded to the nearest integer.", + "type": "boolean" + }, + "tickSize": { + "description": "The size in pixels of axis ticks.", + "minimum": 0, + "type": "number" + }, + "tickWidth": { + "description": "The width, in pixels, of ticks.", + "minimum": 0, + "type": "number" + }, + "ticks": { + "description": "Boolean value that determines whether the axis should include ticks.", + "type": "boolean" + }, + "titleAlign": { + "description": "Horizontal text alignment of axis titles.", + "type": "string" + }, + "titleAngle": { + "description": "Angle in degrees of axis titles.", + "type": "number" + }, + "titleBaseline": { + "description": "Vertical text baseline for axis titles.", + "type": "string" + }, + "titleColor": { + "description": "Color of the title, can be in hex color code or regular color name.", + "type": "string" + }, + "titleFont": { + "description": "Font of the title. (e.g., `\"Helvetica Neue\"`).", + "type": "string" + }, + "titleFontSize": { + "description": "Font size of the title.", + "minimum": 0, + "type": "number" + }, + "titleFontWeight": { + "description": "Font weight of the title. (e.g., `\"bold\"`).", + "type": [ + "string", + "number" + ] + }, + "titleLimit": { + "description": "Maximum allowed pixel width of axis titles.", + "type": "number" + }, + "titleMaxLength": { + "description": "Max length for axis title if the title is automatically generated from the field's description.", + "type": "number" + }, + "titlePadding": { + "description": "The padding, in pixels, between title and axis.", + "type": "number" + }, + "titleX": { + "description": "X-coordinate of the axis title relative to the axis group.", + "type": "number" + }, + "titleY": { + "description": "Y-coordinate of the axis title relative to the axis group.", + "type": "number" + } + }, + "type": "object" + }, + "VgBinding": { + "anyOf": [ + { + "$ref": "#/definitions/VgCheckboxBinding" + }, + { + "$ref": "#/definitions/VgRadioBinding" + }, + { + "$ref": "#/definitions/VgSelectBinding" + }, + { + "$ref": "#/definitions/VgRangeBinding" + }, + { + "$ref": "#/definitions/VgGenericBinding" + } + ] + }, + "VgCheckboxBinding": { + "additionalProperties": false, + "properties": { + "element": { + "type": "string" + }, + "input": { + "enum": [ + "checkbox" + ], + "type": "string" + } + }, + "required": [ + "input" + ], + "type": "object" + }, + "VgEventStream": { + }, + "VgGenericBinding": { + "additionalProperties": false, + "properties": { + "element": { + "type": "string" + }, + "input": { + "type": "string" + } + }, + "required": [ + "input" + ], + "type": "object" + }, + "VgMarkConfig": { + "additionalProperties": false, + "properties": { + "align": { + "$ref": "#/definitions/HorizontalAlign", + "description": "The horizontal alignment of the text. One of `\"left\"`, `\"right\"`, `\"center\"`." + }, + "angle": { + "description": "The rotation angle of the text, in degrees.", + "maximum": 360, + "minimum": 0, + "type": "number" + }, + "baseline": { + "$ref": "#/definitions/VerticalAlign", + "description": "The vertical alignment of the text. One of `\"top\"`, `\"middle\"`, `\"bottom\"`.\n\n__Default value:__ `\"middle\"`" + }, + "dx": { + "description": "The horizontal offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "dy": { + "description": "The vertical offset, in pixels, between the text label and its anchor point. The offset is applied after rotation by the _angle_ property.", + "type": "number" + }, + "fill": { + "description": "Default Fill Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "fillOpacity": { + "description": "The fill opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "font": { + "description": "The typeface to set the text in (e.g., `\"Helvetica Neue\"`).", + "type": "string" + }, + "fontSize": { + "description": "The font size, in pixels.", + "minimum": 0, + "type": "number" + }, + "fontStyle": { + "$ref": "#/definitions/FontStyle", + "description": "The font style (e.g., `\"italic\"`)." + }, + "fontWeight": { + "anyOf": [ + { + "$ref": "#/definitions/FontWeight" + }, + { + "$ref": "#/definitions/FontWeightNumber" + } + ], + "description": "The font weight (e.g., `\"bold\"`)." + }, + "interpolate": { + "$ref": "#/definitions/Interpolate", + "description": "The line interpolation method to use for line and area marks. One of the following:\n- `\"linear\"`: piecewise linear segments, as in a polyline.\n- `\"linear-closed\"`: close the linear segments to form a polygon.\n- `\"step\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"step-before\"`: alternate between vertical and horizontal segments, as in a step function.\n- `\"step-after\"`: alternate between horizontal and vertical segments, as in a step function.\n- `\"basis\"`: a B-spline, with control point duplication on the ends.\n- `\"basis-open\"`: an open B-spline; may not intersect the start or end.\n- `\"basis-closed\"`: a closed B-spline, as in a loop.\n- `\"cardinal\"`: a Cardinal spline, with control point duplication on the ends.\n- `\"cardinal-open\"`: an open Cardinal spline; may not intersect the start or end, but will intersect other control points.\n- `\"cardinal-closed\"`: a closed Cardinal spline, as in a loop.\n- `\"bundle\"`: equivalent to basis, except the tension parameter is used to straighten the spline.\n- `\"monotone\"`: cubic interpolation that preserves monotonicity in y." + }, + "limit": { + "description": "The maximum length of the text mark in pixels (default 0, indicating no limit). The text value will be automatically truncated if the rendered size exceeds the limit.", + "type": "number" + }, + "opacity": { + "description": "The overall opacity (value between [0,1]).\n\n__Default value:__ `0.7` for non-aggregate plots with `point`, `tick`, `circle`, or `square` marks or layered `bar` charts and `1` otherwise.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "orient": { + "$ref": "#/definitions/Orient", + "description": "The orientation of a non-stacked bar, tick, area, and line charts.\nThe value is either horizontal (default) or vertical.\n- For bar, rule and tick, this determines whether the size of the bar and tick\nshould be applied to x or y dimension.\n- For area, this property determines the orient property of the Vega output.\n- For line, this property determines the sort order of the points in the line\nif `config.sortLineBy` is not specified.\nFor stacked charts, this is always determined by the orientation of the stack;\ntherefore explicitly specified value will be ignored." + }, + "radius": { + "description": "Polar coordinate radial offset, in pixels, of the text label from the origin determined by the `x` and `y` properties.", + "minimum": 0, + "type": "number" + }, + "shape": { + "description": "The default symbol shape to use. One of: `\"circle\"` (default), `\"square\"`, `\"cross\"`, `\"diamond\"`, `\"triangle-up\"`, or `\"triangle-down\"`, or a custom SVG path.\n\n__Default value:__ `\"circle\"`", + "type": "string" + }, + "size": { + "description": "The pixel area each the point/circle/square.\nFor example: in the case of circles, the radius is determined in part by the square root of the size value.\n\n__Default value:__ `30`", + "minimum": 0, + "type": "number" + }, + "stroke": { + "description": "Default Stroke Color. This has higher precedence than config.color\n\n__Default value:__ (None)", + "type": "string" + }, + "strokeDash": { + "description": "An array of alternating stroke, space lengths for creating dashed or dotted lines.", + "items": { + "type": "number" + }, + "type": "array" + }, + "strokeDashOffset": { + "description": "The offset (in pixels) into which to begin drawing with the stroke dash array.", + "type": "number" + }, + "strokeOpacity": { + "description": "The stroke opacity (value between [0,1]).\n\n__Default value:__ `1`", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "strokeWidth": { + "description": "The stroke width, in pixels.", + "minimum": 0, + "type": "number" + }, + "tension": { + "description": "Depending on the interpolation type, sets the tension parameter (for line and area marks).", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "text": { + "description": "Placeholder text if the `text` channel is not specified", + "type": "string" + }, + "theta": { + "description": "Polar coordinate angle, in radians, of the text label from the origin determined by the `x` and `y` properties. Values for `theta` follow the same convention of `arc` mark `startAngle` and `endAngle` properties: angles are measured in radians, with `0` indicating \"north\".", + "type": "number" + } + }, + "type": "object" + }, + "VgRadioBinding": { + "additionalProperties": false, + "properties": { + "element": { + "type": "string" + }, + "input": { + "enum": [ + "radio" + ], + "type": "string" + }, + "options": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "input", + "options" + ], + "type": "object" + }, + "VgRangeBinding": { + "additionalProperties": false, + "properties": { + "element": { + "type": "string" + }, + "input": { + "enum": [ + "range" + ], + "type": "string" + }, + "max": { + "type": "number" + }, + "min": { + "type": "number" + }, + "step": { + "type": "number" + } + }, + "required": [ + "input" + ], + "type": "object" + }, + "VgScheme": { + "additionalProperties": false, + "properties": { + "count": { + "type": "number" + }, + "extent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "scheme" + ], + "type": "object" + }, + "VgSelectBinding": { + "additionalProperties": false, + "properties": { + "element": { + "type": "string" + }, + "input": { + "enum": [ + "select" + ], + "type": "string" + }, + "options": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "input", + "options" + ], + "type": "object" + }, + "VgTitleConfig": { + "additionalProperties": false, + "properties": { + "anchor": { + "$ref": "#/definitions/Anchor", + "description": "The anchor position for placing the title. One of `\"start\"`, `\"middle\"`, or `\"end\"`. For example, with an orientation of top these anchor positions map to a left-, center-, or right-aligned title.\n\n__Default value:__ `\"middle\"` for [single](spec.html) and [layered](layer.html) views.\n`\"start\"` for other composite views.\n\n__Note:__ [For now](https://github.com/vega/vega-lite/issues/2875), `anchor` is only customizable only for [single](spec.html) and [layered](layer.html) views. For other composite views, `anchor` is always `\"start\"`." + }, + "angle": { + "description": "Angle in degrees of title text.", + "type": "number" + }, + "baseline": { + "$ref": "#/definitions/VerticalAlign", + "description": "Vertical text baseline for title text." + }, + "color": { + "description": "Text color for title text.", + "type": "string" + }, + "font": { + "description": "Font name for title text.", + "type": "string" + }, + "fontSize": { + "description": "Font size in pixels for title text.\n\n__Default value:__ `10`.", + "minimum": 0, + "type": "number" + }, + "fontWeight": { + "anyOf": [ + { + "$ref": "#/definitions/FontWeight" + }, + { + "$ref": "#/definitions/FontWeightNumber" + } + ], + "description": "Font weight for title text." + }, + "limit": { + "description": "The maximum allowed length in pixels of legend labels.", + "minimum": 0, + "type": "number" + }, + "offset": { + "description": "Offset in pixels of the title from the chart body and axes.", + "type": "number" + }, + "orient": { + "$ref": "#/definitions/TitleOrient", + "description": "Default title orientation (\"top\", \"bottom\", \"left\", or \"right\")" + } + }, + "type": "object" + }, + "ViewConfig": { + "additionalProperties": false, + "properties": { + "clip": { + "description": "Whether the view should be clipped.", + "type": "boolean" + }, + "fill": { + "description": "The fill color.\n\n__Default value:__ (none)", + "type": "string" + }, + "fillOpacity": { + "description": "The fill opacity (value between [0,1]).\n\n__Default value:__ (none)", + "type": "number" + }, + "height": { + "description": "The default height of the single plot or each plot in a trellis plot when the visualization has a continuous (non-ordinal) y-scale with `rangeStep` = `null`.\n\n__Default value:__ `200`", + "type": "number" + }, + "stroke": { + "description": "The stroke color.\n\n__Default value:__ (none)", + "type": "string" + }, + "strokeDash": { + "description": "An array of alternating stroke, space lengths for creating dashed or dotted lines.\n\n__Default value:__ (none)", + "items": { + "type": "number" + }, + "type": "array" + }, + "strokeDashOffset": { + "description": "The offset (in pixels) into which to begin drawing with the stroke dash array.\n\n__Default value:__ (none)", + "type": "number" + }, + "strokeOpacity": { + "description": "The stroke opacity (value between [0,1]).\n\n__Default value:__ (none)", + "type": "number" + }, + "strokeWidth": { + "description": "The stroke width, in pixels.\n\n__Default value:__ (none)", + "type": "number" + }, + "width": { + "description": "The default width of the single plot or each plot in a trellis plot when the visualization has a continuous (non-ordinal) x-scale or ordinal x-scale with `rangeStep` = `null`.\n\n__Default value:__ `200`", + "type": "number" + } + }, + "type": "object" + } + } +} \ No newline at end of file