-
Notifications
You must be signed in to change notification settings - Fork 15
feat: experimental API to expose Draco features through a single class #949
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
peter-gy
wants to merge
52
commits into
main
Choose a base branch
from
peter-gy/dracox
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
`vertical_scrolling_y:` Just as we don't want horiztonal scrolling, we also don't want vertical scrolling. `vertical_scrolling_row`: Without this, we recommend row-faceted charts over fields of 1k+ cardinality, killing vega lite in the process.
Adds a heavy penalty for faceting over `N > 12` cardinality field. Helps us reduce cases where Vega Lite rendering is broken due to choosing a field with 1k+ unique values to facet over.
We don't want to use same mark to encode different fields and we also don't want to overload same channel in the same view.
- increase weight - increase threshold from 15 to 40 so that we don't start binning too "soon"
Advocates for using categorical scale for low-cardinality fields
Preference to use line mark together with datetime field
false good ideas happen...
Allows us to recommend log scale for highly skewed numeric fields
Prefer using log scale for skewed fields with positive values
- Introduced `total_facet_cardinality` helper to calculate the total cardinality of facets in a view. - Updated `facet_used_before_color` preference to consider numeric field cardinality. - Added `underplotting` preference to avoid faceting with too few data points. - Implemented `continuous_scale` preference for linear, log, or symlog scales on continuous variables. - Adjusted weights for various preferences to reflect new logic.
…ggregate_weight` - Removed cardinality check for numeric fields in `facet_used_before_color` preference. - Increased `aggregate_weight` from 1 to 2 to reflect updated logic.
Also modified `bin` to consider binning within facets
…al data - Introduced preferences to prefer faceting over color for line/point marks and to avoid using ordinal scale with color for high cardinality categorical data. - Added corresponding weights for the new preferences to ensure proper prioritization.
When task is `value`, binning is counter-productive
…ore facet - Introduced a new preference to prioritize faceting on both row and column before using color for point marks with high cardinality to mitigate overplotting. - Added corresponding weight to ensure proper prioritization of this preference.
Allows us to set label spacing, etc. as a function of min and max of string field
- Changed the skew threshold for determining if a field is skewed from 5 to 2. - Updated weights for row and column facets, setting `facet_row_weight` to 0 and `facet_col_weight` to 1 to prioritize row facets.
Slight penalty for using colored point in discrete-discrete setting. This way we favor heatmaps and bubble plots.
Introduced preferences to favor shorter max label lengths for row facets and longer max label lengths for column facets to enhance readability.
Introduced a new preference to avoid using text channels for fields with high cardinality and updated the corresponding weight to prioritize this preference in the layout.
add preferences for line mark with multiple number fields and color encoding for point mark
|
Looks like there are a few issues preventing this PR from being merged!
If you'd like me to help, just leave a comment, like
Feel free to include any additional details that might help me get this PR into a better state. You can manage your notification settings |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
CHANGELOG
DracoExpressclass acting as a facade over Draco fetauresDracoChartSpec.DracoExpress.with_weights) and modified programs (DracoExpress.with_draco). This is really useful for inspecting how a given set of weights and design rules impact recommendations and the API makes it easier to experiment with different configs in a reactive environment like Marimoexpress.lpprovides a simpler ASP API allowing us to construct more terse Draco programs where we want to anchor over a certain field, encoding, mark, etc. This is extremely useful in scenarios where a user (or an LLM) comes up with certain non-negotiable details about a vis (e.g.date,windandconditionfields must be used andconditionmust be used for faceting) and let Draco complete all the remaining parts of the spec optimally.Let's say I use Draco with a specific set of weights and specific ASP core to recommend charts for my dataset. A state dump captures all the required context (
dataover which recommendations were made,programsformalizing the design guidelines based on which charts are recommended andweightsdefining chart feature priorities:[ { "data": [ { "publication_year": "1990-01-01", "conference": "Vis", "paper_count": 53 }, { "publication_year": "1991-01-01", "conference": "Vis", "paper_count": 57 }, { "publication_year": "1992-01-01", "conference": "Vis", "paper_count": 59 }, ... ], "programs": [ { "name": "define", "src": "% ====== Definitions of valid function domains ======\n\n% @definition(mark_type) Types of marks to encode data.\ndomain((mark,type),(point;bar;line;area;text;tick;rect)).\n\n% @definition(view_coordinate) Types of coordinates.\ndomain((view,coordinates),(cartesian;polar)).\n\n% @definition(field_type) Basic types of the data.\ndomain((field,type),(string;number;boolean;datetime)).\n\n% @definition(field_names) Names of the data.\ndomain((field,name),F) :- attribute((field,name),_,F).\n\n% @definition(encoding_fields) Encoding fields are defined by field names.\ndomain((encoding,field),N) :- attribute((field,name),_,N).\n\n% @definition(aggregate) Aggregation functions.\ndomain((encoding,aggregate),(count;mean;median;min;max;stdev;sum)).\ndomain((encoding,aggregate),(count;sum)).\n\n% @definition(binning) Numbers of bins that can be recommended, any natural number is allowed.\ndomain(binCount,(10;25;200)).\ndomain((encoding,binning),N) :- domain(binCount,N).\n\n% @definition(channel) Encoding channels.\ndomain(positional,(x;y)).\ndomain(non_positional,(color;size;shape;text;detail)).\ndomain(single_channel,C) :- domain(positional,C).\ndomain(single_channel,C) :- domain(non_positional,C).\ndomain(multi_channel,detail).\ndomain(channel,C) :- domain(single_channel,C).\ndomain(channel,C) :- domain(multi_channel,C).\n\ndomain((encoding,channel),C) :- domain(channel,C).\ndomain((scale,channel),C) :- domain(channel,C).\n\n% @definition(encoding_channel) All channels in the mark's encodings.\ndomain(encoding_channel,V,C) :-\n entity(mark,V,M),\n entity(encoding,M,E),\n attribute((encoding,channel),E,C).\n\n% @definition(scale_encoding) All channels in the view's and root's scales.\ndomain(scale_channel,V,C) :-\n entity(scale,V,S),\n attribute((scale,channel),S,C).\ndomain(scale_channel,V,C) :-\n entity(view,R,V),\n entity(scale,R,S),\n attribute((scale,channel),S,C).\n\n% @definition(scale_type) Scale types.\ndomain(discrete_scale,(ordinal;categorical)).\ndomain(continuous_scale,(log;symlog;linear)).\ndomain(scale_type,T) :- domain(discrete_scale,T).\ndomain(scale_type,T) :- domain(continuous_scale,T).\n\ndomain((scale,type),T) :- domain(scale_type,T).\n\n% @definition(task) Tasks.\ndomain(task,(value;summary)).\n\n% @definition(stack) Stacking methods.\ndomain((encoding,stack),(zero;center;normalize)).\n\n% @definition(scale_zero) Scale domain includes 0.\ndomain((scale,zero),true).\n\n% @definition(facets) Definition for facets.\ndomain((facet,field),F) :- attribute((field,name),_,F).\ndomain((facet,channel),(row;col)).\ndomain((facet,binning),N) :- domain(binCount,N).\n\n% @definition(interesting) Definition for fields that are relevant to the task.\ndomain((field,interesting),true).\n" }, { "name": "constraints", "src": "% ====== Constraints ======\n\n% @definition(invalid_domain) Only valid domain values are allowed.\nviolation(invalid_domain) :- attribute(P,_,V), domain(P,_), not domain(P,V).\n\n% @definition(duplicate_attribute) Do not define the same attribute twice.\nviolation(duplicate_attribute) :- attribute(P,O,V1), attribute(P,O,V2), domain(P,_), V1<V2.\n\n% @definition(duplicate_field_name) Do not allow same field names.\nviolation(duplicate_field_name) :- attribute((field,name),F1,N), attribute((field,name),F2,N), F1<F2.\n\n% @definition(valid_fields) Only allow fields that have been defined.\nviolation(existing_field) :- attribute(((encoding;facet),field),_,N), not attribute((field,name),_,N).\n\n% @definition(attribute_entity) The type of an attribute path should link to the correct entity.\nviolation(attribute_entity) :- attribute((P,_),E,_), entity(_,_,E), not entity(P,_,E).\n\n% @definition(violation) No violations allowed.\n:- violation(_).\n" }, { "name": "helpers", "src": "% ====== Helpers ======\n\n% @helper(mark_channel) Whether a mark has an encoding for a channel.\nhelper((mark,channel),M,C) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,C).\n\n% @helper(mark_with_stack) Whether a mark has an encoding with stacking.\nhelper(mark_with_stack,M) :-\n entity(encoding,M,E),\n attribute((encoding,stack),E,_).\n\n% @helper(mark_scale) Whether a mark has a scale.\nhelper((mark,scale),M,S) :-\n entity(mark,V,M),\n entity(scale,V,S).\nhelper((mark,scale),M,S) :-\n entity(view,R,V),\n entity(mark,V,M),\n entity(scale,R,S).\n\n% @helper(mark_scale_channel) Whether a mark has a scale type for a channel.\nhelper(mark_scale_channel,M,T,C) :-\n helper((mark,scale),M,S),\n attribute((scale,channel),S,C),\n attribute((scale,type),S,T).\n\n% @helper(mark_encoding_scale) Link mark, encoding, and scale together.\nhelper(mark_encoding_scale,M,E,S) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n helper((mark,scale),M,S),\n attribute((scale,channel),S,C).\n\n% @helper(encoding_type) Whether an encoding has a channel for a scale type.\nhelper((encoding,scale_type),E,T) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n helper(mark_scale_channel,M,T,C).\n\n% @helper(mark_channel_field) Whether a mark has an encoding for a field and a channel.\nhelper(mark_channel_field,M,C,F) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n helper((encoding,field),E,F).\n\n% @helper(mark_channel_discrete_or_binned) Whether a mark has a discrete scale or binned encoding for a channel.\nhelper(mark_channel_discrete_or_binned,M,C) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n helper(mark_scale_channel,M,T,C),\n domain(discrete_scale,T).\nhelper(mark_channel_discrete_or_binned,M,C) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n attribute((encoding,binning),E,_).\n\n% @helper(mark_encoding_discrete_or_binned) Whether a mark has a discrete scale or binned encoding for an encoding.\nhelper(mark_encoding_discrete_or_binned,M,E) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n helper(mark_scale_channel,M,T,C),\n domain(discrete_scale,T).\nhelper(mark_encoding_discrete_or_binned,M,E) :-\n entity(encoding,M,E),\n attribute((encoding,binning),E,_).\n\n% @helper(mark_encoding_cont) Whether a mark has a continuous for an encoding.\nhelper(mark_encoding_cont,M,E) :-\n entity(encoding,M,E),\n not helper(mark_encoding_discrete_or_binned,M,E).\n\n% @helper(mark_channel_cont) Whether a mark has a continuous scale for a channel.\nhelper(mark_channel_cont,M,C) :-\n helper((mark,channel),M,C),\n not helper(mark_channel_discrete_or_binned,M,C).\n\n% @helper(encoding_cardinality) The cardinality of an encoding.\nhelper(encoding_cardinality,E,N) :-\n attribute((field,unique),F,N),\n helper((encoding,field),E,F),\n not attribute((encoding,binning),E,_).\nhelper(encoding_cardinality,E,N) :-\n attribute((encoding,binning),E,N).\n\n% @helper(discrete_cardinality) The cardinality of a discrete encoding.\nhelper(discrete_cardinality,M,E,N) :-\n helper(encoding_cardinality,E,N),\n helper(mark_encoding_discrete_or_binned,M,E).\n\n% @helper(facet_cardinality,Fa,N) The cardinality of a facet.\nhelper(facet_cardinality,Fa,N) :-\n attribute((field,unique),Fi,N),\n helper((facet,field),Fa,Fi),\n not attribute((facet,binning),Fa,_).\nhelper(facet_cardinality,Fa,N) :-\n attribute((facet,binning),Fa,N).\n\n% @helper(is_c_c) Continuous by continuous.\nhelper(is_c_c,M) :-\n entity(mark,_,M),\n helper((mark,channel),M,x),\n helper((mark,channel),M,y),\n not helper(mark_channel_discrete_or_binned,M,x),\n not helper(mark_channel_discrete_or_binned,M,y).\n\n% @helper(is_c_d) Continuous by discrete (or only one continuous).\nhelper(is_c_d,M) :-\n entity(mark,_,M),\n helper(mark_channel_cont,M,x),\n not helper(mark_channel_cont,M,y).\nhelper(is_c_d,M) :-\n entity(mark,_,M),\n not helper(mark_channel_cont,M,x),\n helper(mark_channel_cont,M,y).\n\n% @helper(is_d_d) Discrete by discrete.\nhelper(is_d_d,M) :-\n helper((mark,channel),M,x),\n helper((mark,channel),M,y),\n helper(mark_channel_discrete_or_binned,M,x),\n helper(mark_channel_discrete_or_binned,M,y).\nhelper(is_d_d,M) :-\n helper((mark,channel),M,x),\n helper(mark_channel_discrete_or_binned,M,x),\n not helper((mark,channel),M,y).\nhelper(is_d_d,M) :-\n helper((mark,channel),M,y),\n helper(mark_channel_discrete_or_binned,M,y),\n not helper((mark,channel),M,x).\n\n% @helper(non_pos_unaggregated) Non-positional channels are unaggregated.\nhelper(non_pos_unaggregated,M) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n domain(non_positional,C),\n not attribute((encoding,aggregate),E,_).\n\n% @helper(no_overlap) The continuous variable is a measure (it is aggregated) and all other non-positional channels are aggregated, or we use stack.\nhelper(no_overlap,M) :-\n helper(is_c_d,M),\n helper(mark_encoding_cont,M,E),\n attribute((encoding,channel),E,(x;y)),\n attribute((encoding,aggregate),E,_),\n not helper(non_pos_unaggregated,M).\nhelper(no_overlap,M) :-\n helper(is_c_d,M),\n entity(encoding,M,E),\n attribute((encoding,stack),E,_).\nhelper(no_overlap,M) :-\n helper(is_c_d,M),\n attribute(number_rows,root,N),\n helper(discrete_size,M,N).\nhelper(no_overlap,M) :-\n helper(is_d_d,M),\n helper((mark,channel),M,C),\n domain(non_positional,C),\n not helper(non_pos_unaggregated,M).\nhelper(no_overlap,M) :-\n helper(is_d_d,M),\n attribute(number_rows,root,N1),\n helper(discrete_size,M,N2),\n N1 <= N2.\n\n% @helper(overlap) We definitely overlap if the data size > discrete size.\nhelper(overlap,M) :-\n helper(is_c_d,M),\n not helper(no_overlap,M),\n attribute(number_rows,root,N1),\n helper(discrete_size,M,N2),\n N1 > N2.\nhelper(overlap,M) :-\n entity(mark,V,M),\n helper(is_d_d,M),\n not helper(no_overlap,M),\n not entity(facet,V,_),\n attribute(number_rows,root,N1),\n helper(discrete_size,M,N2),\n N1 > N2.\n\n% @helper(discrete_size) The size of the discrete positional encoding.\nhelper(discrete_size,M,N) :-\n helper(is_c_d,M),\n helper(x_cardinality,M,N).\nhelper(discrete_size,M,N) :-\n helper(is_c_d,M),\n helper(y_cardinality,M,N).\nhelper(discrete_size,M,1) :-\n helper(is_c_d,M),\n helper(mark_encoding_cont,M,E),\n attribute((encoding,channel),E,x),\n not helper((mark,channel),M,y).\nhelper(discrete_size,M,1) :-\n helper(is_c_d,M),\n helper(mark_encoding_cont,M,E),\n attribute((encoding,channel),E,y),\n not helper((mark,channel),M,x).\nhelper(discrete_size,M,N) :-\n helper(is_d_d,M),\n helper(x_cardinality,M,NX),\n helper(y_cardinality,M,NY),\n N = NX*NY.\n\n% @helper(x_cardinality) Cardinality of discrete x. Helps to go from quadratic to linear number of grounding.\nhelper(x_cardinality,M,N) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,x),\n helper(discrete_cardinality,M,E,N).\nhelper(x_cardinality,M,1) :-\n entity(mark,_,M),\n not helper((mark,channel),M,x).\n\n% @helper(y_cardinality) Cardinality of discrete x. Helps to go from quadratic to linear number of grounding.\nhelper(y_cardinality,M,N) :-\n entity(encoding,M,E),\n attribute((encoding,channel),E,y),\n helper(discrete_cardinality,M,E,N).\nhelper(y_cardinality,M,1) :-\n entity(mark,_,M),\n not helper((mark,channel),M,y).\n\n% @helper(enc_interesting) The field relevant to the task is mapped to the encoding E.\nhelper(enc_interesting,E) :-\n helper((encoding,field),E,F),\n attribute((field,interesting),F,true).\nhelper(enc_interesting,E) :-\n helper((facet,field),E,F), % same field name here\n attribute((field,interesting),F,true).\n\n% @helper(encoding_field) The encoding field name matches the id.\nhelper((encoding,field),E,F) :-\n attribute((encoding,field),E,N),\n attribute((field,name),F,N).\n\n% @helper(facet_field) The encoding field name matches the id.\nhelper((facet,field),FC,F) :-\n attribute((facet,field),FC,N),\n attribute((field,name),F,N).\n\n% @helper(field_skewed) Whether a field is skewed (absolute skew > 5).\nhelper(field_skewed,F) :-\n attribute((field,skew),F,S),\n |S| > 2.\n\n% @helper(total_facet_cardinality) The total cardinality of all facets in a view (product of individual facet cardinalities).\nhelper(total_facet_cardinality,V,1) :-\n entity(view,root,V),\n not entity(facet,V,_).\nhelper(total_facet_cardinality,V,N) :-\n entity(facet,V,F),\n attribute((facet,channel),F,row),\n helper(facet_cardinality,F,N),\n not entity(facet,V,F2),\n F2 != F,\n attribute((facet,channel),F2,col).\nhelper(total_facet_cardinality,V,N) :-\n entity(facet,V,F),\n attribute((facet,channel),F,col),\n helper(facet_cardinality,F,N),\n not entity(facet,V,F2),\n F2 != F,\n attribute((facet,channel),F2,row).\nhelper(total_facet_cardinality,V,N) :-\n entity(facet,V,F1),\n entity(facet,V,F2),\n F1 != F2,\n attribute((facet,channel),F1,row),\n attribute((facet,channel),F2,col),\n helper(facet_cardinality,F1,N1),\n helper(facet_cardinality,F2,N2),\n N = N1 * N2.\n" }, { "name": "generate", "src": "% ====== Generators ======\n\n% helpers to generate attributes based on whether they are required.\n{ attribute(N,root,V) : domain(N,V) } = 1 :- root_required(N).\n{ attribute((N,A),E,V): domain((N,A),V) } = 1 :- entity(N,_,E), required((N,A)).\n0 { attribute((N,A),E,V): domain((N,A),V) } 1 :- entity(N,_,E), not_required((N,A)).\n\n% maximum number of non-layered views.\n#const max_views = 1.\n{ entity(view,root,(v,0..max_views-1)) }.\n:- { entity(view,root,_) } > max_views.\n:- entity(view,root,(v,ID)), not entity(view,root,(v,ID-1)), ID > 0.\n\n% @generator(coordinates) Each view requires coordinates.\nrequired((view,coordinates)).\n\n% maximum number of additional marks for each view.\n#const max_marks = 2.\n{ mark_id(V,0..max_marks-1) } :- entity(view,root,V).\n:- mark_id(V,ID), not mark_id(V,ID-1), ID > 0.\nentity(mark,V,(V,M)) :- mark_id(V,M).\n\n% maximum number for additional encoding channels.\n#const max_encs = 4.\n{ enc_id(M,0..max_encs-1) } :- entity(mark,V,M).\n:- enc_id(M,ID), not enc_id(M,ID-1), ID > 0.\nentity(encoding,M,(M,E)) :- enc_id(M,E).\n\n% @generate(encoding_channel) Each encoding requires a channel.\nrequired((encoding,channel)).\n% @generator(mark_type) Each mark requires a type.\nrequired((mark,type)).\n% @generator(task) Each root requires a task.\nroot_required(task).\n\n% @generator(encoding_attribute) Encoding with binning, aggregate, field or stack.\nnot_required((encoding,binning)).\nnot_required((encoding,aggregate)).\nnot_required((encoding,stack)).\n0 { attribute((encoding,field),E,N): domain((field,name),N) } 1 :- entity(encoding,_,E).\n\n% generator(scale) generate scales such that their ids and channels corresponds to the encodings in each view.\n{ entity(scale,V,(s,E)) } :- entity(mark,V,M), entity(encoding,M,E), attribute((encoding,channel),E,C).\nattribute((scale,channel),(s,E),C) :- entity(scale,V,(s,E)), attribute((encoding,channel),E,C).\n\nrequired((scale,channel)).\nrequired((scale,type)).\nnot_required((scale,zero)).\n\n% generator(facet) Each specification can have at most both row and col facet.\nfacet_id(0..1).\n{ entity(facet,V,(fc,F)) : facet_id(F), entity(view,root,V) }.\n:- entity(facet,V,(fc,1)), not entity(facet,V,(fc,0)),facet_id(1), facet_id(0).\n\nrequired((facet,channel)).\nnot_required((facet,binning)).\n{ attribute((facet,field),E,N): domain((field,name),N) } = 1 :- entity(facet,_,E).\n" }, { "name": "hard", "src": "% ====== Hard constraints ======\n\n% @hard(scale_type_data_type) Primitive type has to support scale type.\nviolation(scale_type_data_type) :-\n attribute((field,type),F,(string;boolean)),\n helper((encoding,field),E,F),\n helper((encoding,scale_type),E,T),\n domain(continuous_scale,T).\n\n% @hard(log_non_positive) Cannot use log if the data is negative or zero.\nviolation(log_non_positive) :-\n attribute((field,min),F,MIN),\n helper((encoding,field),E,F),\n helper((encoding,scale_type),E,log),\n MIN <= 0.\n\n% @hard(log_zero_included) Cannot use the log scale if the extent includes zero.\nviolation(log_zero_included) :-\n attribute((field,min),F,MIN),\n attribute((field,max),F,MAX),\n MIN * MAX <= 0,\n entity(encoding,M,E),\n helper((encoding,field),E,F),\n helper(mark_encoding_scale,M,E,S),\n attribute((scale,type),S,log).\n\n% @hard(bin_and_aggregate) Cannot bin and aggregate.\nviolation(bin_and_aggregate) :-\n attribute((encoding,binning),E,_),\n attribute((encoding,aggregate),E,_).\n\n% @hard(aggregate_t_valid) Temporal scale only supports min and max.\nviolation(aggregate_t_valid) :-\n attribute((field,type),F,datetime),\n helper((encoding,field),E,F),\n attribute((encoding,aggregate),E,A),\n A != min,\n A != max.\n\n% @hard(aggregate_num_valid) Only numbers can be aggregated with mean, sum, stdev\nviolation(aggregate_num_valid) :-\n attribute((field,type),F,T),\n helper((encoding,field),E,F),\n attribute((encoding,aggregate),E,(mean;sum;stdev)),\n T != number.\n\n% @hard(bin_n_d) Only numbers and datetimes can be binned\nviolation(bin_n_d) :-\n attribute((field,type),F,T),\n helper((encoding,field),E,F),\n attribute((encoding,binning),E,_),\n T != number,\n T != datetime.\n\n% @hard(aggregate_detail) Detail cannot be aggregated.\nviolation(aggregate_detail) :-\n attribute((encoding,channel),E,detail),\n attribute((encoding,aggregate),E,_).\n\n% @hard(count_without_q) Count has to have a continuous scale.\nviolation(count_without_q) :-\n attribute((encoding,aggregate),E,count),\n helper((encoding,scale_type),E,T),\n domain(discrete_scale,T).\n\n% @hard(shape_not_ordinal) Shape requires discrete and ordinal (ordinal/categorical doesn't matter as scale types, so we use ordinal only here).\nviolation(shape_not_ordinal) :-\n helper(mark_scale_channel,_,T,shape),\n T != ordinal.\n\n% @hard(categorical_not_color) Categorical only works with color channel.\nviolation(categorical_not_color) :-\n attribute((scale,type),S,categorical),\n not attribute((scale,channel),S,color).\n\n% @hard(size_negative) Do not use size when data is negative as size implies that data is positive.\nviolation(size_negative) :-\n attribute((encoding,channel),E,size),\n helper((encoding,field),E,F),\n attribute((field,min),F,MIN),\n attribute((field,max),F,MAX),\n MIN < 0,\n MAX > 0.\n\n% @hard(encoding_repeat_channel) Cannot use single channels twice for the same mark.\nviolation(encoding_repeat_channel) :-\n entity(mark,_,M),\n domain(single_channel,C),\n 2 <= #count { E : entity(encoding,M,E), attribute((encoding,channel),E,C) }.\n\n% @hard(scale_repeat_channel) Cannot use single channels twice for the same view.\nviolation(scale_repeat_channel) :-\n entity(view,root,V),\n domain(single_channel,C),\n 2 <= #count { S : entity(scale,V,S), attribute((scale,channel),S,C) }.\n\n% @hard(encoding_channel_without_scale) Encoding channel doesn't have a corresponding scale channel.\nviolation(encoding_channel_without_scale) :-\n entity(mark,V,M),\n helper((mark,channel),M,C),\n not domain(scale_channel,V,C).\n\n% @hard(scale_channel_without_encoding) Scale channel doesn't have a corresponding encoding channel.\nviolation(scale_channel_without_encoding) :-\n entity(view,_,V),\n entity(scale,V,S),\n attribute((scale,channel),S,C),\n not domain(encoding_channel,V,C).\n\n% @hard(no_encodings) There has to be at least one encoding for every mark.\nviolation(no_encodings) :-\n entity(mark,_,M),\n not entity(encoding,M,_).\n\n% @hard(encoding_no_field_and_not_count) All encodings (if they have a channel) require field except if we have a count aggregate.\nviolation(encoding_no_field_and_not_count) :-\n entity(encoding,_,E),\n not attribute((encoding,field),E,_),\n not attribute((encoding,aggregate),E,count).\n\n% @hard(count_with_field) Count should not have a field. Having a field doesn't make a difference.\nviolation(count_with_field) :-\n attribute((encoding,aggregate),E,count),\n helper((encoding,field),E,_).\n\n% @hard(text_mark_without_text_channel) Text mark requires text encoding.\nviolation(text_mark_without_text_channel) :-\n attribute((mark,type),M,text),\n not helper((mark,channel),M,text).\n\n% @hard(text_channel_without_text_mark) Text channel requires text mark.\nviolation(text_channel_without_text_mark) :-\n helper((mark,channel),M,text),\n not attribute((mark,type),M,text).\n\n% @hard(point_tick_bar_without_x_or_y) Point, tick, and bar require x or y channel.\nviolation(point_tick_bar_without_x_or_y) :-\n attribute((mark,type),M,(point;tick;bar)),\n not helper((mark,channel),M,x),\n not helper((mark,channel),M,y).\n\n% @hard(line_area_without_x_y) Line and area require x and y channel.\nviolation(line_area_without_x_y) :-\n attribute((mark,type),M,(line;area)),\n { helper((mark,channel),M,x);helper((mark,channel),M,y) } <= 1.\n\n% @hard(line_area_with_discrete) Line and area cannot have both x and y discrete.\nviolation(line_area_with_discrete) :-\n attribute((mark,type),M,(line;area)),\n helper(mark_scale_channel,M,T1,x),\n helper(mark_scale_channel,M,T2,y),\n domain(discrete_scale,T1),\n domain(discrete_scale,T2).\n\n% @hard(bar_tick_continuous_x_y) Bar and tick cannot have both x and y continuous.\nviolation(bar_tick_continuous_x_y) :-\n attribute((mark,type),M,(tick;bar)),\n helper(is_c_c,M).\n\n% @hard(view_scale_conflict) A view cannot have a scale definition that conflicts with a shared scale for the same channel.\nviolation(view_scale_conflict) :-\n entity(view,R,V),\n entity(scale,R,S1),\n entity(scale,V,S2),\n attribute((scale,channel),S1,C),\n attribute((scale,channel),S2,C).\n\n% @hard(shape_without_point) Shape channel requires point mark.\nviolation(shape_without_point) :-\n helper((mark,channel),M,shape),\n not attribute((mark,type),M,point).\n\n% @hard(size_without_point_text) Size only works with some marks.\nviolation(size_without_point_text) :-\n helper((mark,channel),M,size),\n not attribute((mark,type),M,text),\n not attribute((mark,type),M,point).\n\n% @hard(detail_without_agg) Detail requires aggregation. Detail adds a field to the group by. Detail could also be used to add information to tooltips. We may remove this later.\nviolation(detail_without_agg) :-\n entity(encoding,M,E1),\n entity(encoding,M,E2),\n E1 != E2,\n attribute((encoding,channel),E1,detail),\n not attribute((encoding,aggregate),E2,_).\n\n% @hard(area_bar_with_log) Do not use log for bar or area mark as they are often misleading. We may remove this rule in the future.\nviolation(area_bar_with_log) :-\n attribute((mark,type),M,(bar;area)),\n helper(mark_scale_channel,M,log,(x;y)).\n\n% @hard(rect_without_d_d) Rect mark needs discrete x and y.\nviolation(rect_without_d_d) :-\n attribute((mark,type),M,rect),\n helper(mark_scale_channel,M,T,(x;y)),\n domain(continuous_scale,T).\n\n% @hard(same_field_x_and_y) Don't use the same field on x and y.\nviolation(same_field_x_and_y) :-\n helper(mark_channel_field,M,x,F),\n helper(mark_channel_field,M,y,F),\n entity(field,root,F),\n entity(mark,_,M).\n\n% @hard(count_twice) Don't use count twice.\nviolation(count_twice):-\n { entity(encoding,M,E) : attribute((encoding,aggregate),E,count) } >= 2,\n entity(mark,_,M).\n\n% @hard(aggregate_not_all_continuous) If we use aggregation, then all continuous scales need to be aggregated.\nviolation(aggregate_not_all_continuous):-\n attribute((encoding,aggregate),E1,_),\n entity(encoding,M,E1),\n entity(encoding,M,E2),\n E1 > E2,\n helper((encoding,scale_type),E2,T),\n domain(continuous_scale,T),\n not attribute((encoding,binning),E2,_),\n not attribute((encoding,aggregate),E2,_).\n\n% @hard(detail_not_ordinal) Detail requires ordinal scales.\nviolation(detail_not_ordinal) :-\n attribute((scale,channel),S,detail),\n not attribute((scale,type),S,ordinal).\n\n% @hard(bar_tick_area_line_without_continuous_x_y) Bar, tick, line, area require some continuous variable on x or y.\nviolation(bar_tick_area_line_without_continuous_x_y) :-\n attribute((mark,type),M,(bar;tick;area;line)),\n { helper(mark_channel_cont,M,x);helper(mark_channel_cont,M,y) } <= 0.\n\n% @hard(zero_d_n) Can only use zero with datetime or number.\nviolation(zero_d_n) :-\n helper((mark,scale),M,S),\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n attribute((scale,zero),S,_),\n attribute((scale,channel),S,C),\n helper((encoding,field),E,F),\n attribute((field,type),F,T),\n T != datetime,\n T != number.\n\n% @hard(zero_linear) Can only use zero with linear scale.\nviolation(zero_linear) :-\n entity(scale,_,S),\n not attribute((scale,type),S,linear),\n attribute((scale,zero),S,true).\n\n% @hard(bar_area_without_zero) Bar and area mark requires scale of continuous to start at zero.\nviolation(bar_area_without_zero) :-\n attribute((mark,type),M,(bar;area)),\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n not attribute((encoding,binning),E,_),\n helper((mark,scale),M,S),\n attribute((scale,channel),S,C),\n attribute((scale,type),S,T),\n domain(continuous_scale,T),\n not attribute((scale,zero),S,_),\n C = (x;y).\n\n% @hard(row_no_y) Don't use row without y. Just using y is simpler.\nviolation(row_no_y) :-\n entity(facet,V,F),\n attribute((facet,channel),F,row),\n entity(mark,V,M),\n not helper((mark,channel),M,y).\n\n% @hard(col_no_x) Don't use column without x. Just using x is simpler.\nviolation(col_no_x) :-\n entity(facet,V,F),\n attribute((facet,channel),F,col),\n entity(mark,V,M),\n not helper((mark,channel),M,x).\n\n% @hard(facet_no_duplicate_field) Don't use the same field twice when faceting.\nviolation(facet_no_duplicate_field) :-\n entity(facet,V,F1),\n entity(facet,V,F2),\n F1 != F2,\n attribute((facet,field),F1,F),\n attribute((facet,field),F2,F).\n\n% @hard(facet_no_duplicate_channel_on_same_view) Don't use the same channel twice for faceting on the same view.\nviolation(facet_no_duplicate_channel_on_same_view) :-\n entity(view,root,V),\n entity(facet,V,F1),\n entity(facet,V,F2),\n F1 != F2,\n attribute((facet,channel),F1,C),\n attribute((facet,channel),F2,C).\n\n% @hard(stack_without_bar_area) Only use stacking for bar and area.\nviolation(stack_without_bar_area) :-\n helper(mark_with_stack,M),\n not attribute((mark,type),M,bar),\n not attribute((mark,type),M,area).\n\n% @hard(stack_without_summative_agg) Don't stack if aggregation is not summative (summative are count, sum, distinct, valid, missing).\nviolation(stack_without_summative_agg) :-\n entity(encoding,_,E),\n attribute((encoding,stack),E,_),\n not attribute((encoding,aggregate),E,sum),\n not attribute((encoding,aggregate),E,count).\n\n% @hard(no_stack_with_bar_area_discrete_color) Need to stack if we use bar, area with discrete color.\nviolation(no_stack_with_bar_area_discrete_color) :-\n helper(mark_channel_discrete_or_binned,M,color),\n attribute((mark,type),M,(bar;area)),\n not helper(mark_with_stack,M).\n\n% @hard(stack_without_discrete_color_or_detail) Can only use stack if we also use discrete color, or detail.\nviolation(stack_without_discrete_color_or_detail) :-\n helper(mark_with_stack,M),\n not helper(mark_channel_discrete_or_binned,M,color),\n not helper((mark,channel),M,detail).\n\n% @hard(stack_without_x_y) Stack can only be on x or y.\nviolation(stack_without_x_y) :-\n attribute((encoding,stack),E,_),\n not attribute((encoding,channel),E,x),\n not attribute((encoding,channel),E,y).\n\n% @hard(stack_discrete) Stack can only be on continuous.\nviolation(stack_discrete) :-\n attribute((encoding,channel),E,C),\n attribute((encoding,stack),E,_),\n helper(mark_channel_discrete_or_binned,_,C).\n\n% @hard(stack_with_non_positional_non_agg) Cannot use non positional continuous with stack unless it's aggregated.\nviolation(stack_with_non_positional_non_agg) :-\n helper(mark_with_stack,M),\n entity(encoding,M,E),\n attribute((encoding,channel),E,C),\n domain(non_positional,C),\n not attribute((encoding,aggregate),E,_),\n not helper(mark_channel_discrete_or_binned,M,C).\n\n% @hard(invalid_bin) Check bin type.\nviolation(invalid_bin) :-\n attribute((encoding,binning),_,B),\n B < 0.\n\n% @hard(invalid_num_rows) number_rows has to be larger than 0.\nviolation(invalid_num_rows) :-\n attribute(number_rows,root,R),\n R <= 0.\n\n% @hard(invalid_unique) The number of unique values has to be larger than 0.\nviolation(invalid_unique) :-\n attribute((field,unique),_,U),\n U <= 0.\n\n% @hard(invalid_extent_non_number) Extent only allowed for numbers (for now).\nviolation(invalid_extent_non_number) :-\n attribute((field,(min;max)),F,_),\n not attribute((field,type),F,number).\n\n% @hard(invalid_non_number_std) Std only allowed for numbers (for now).\nviolation(invalid_non_number_std) :-\n attribute((field,std),F,_),\n not attribute((field,type),F,number).\n\n% @hard(invalid_std) Std has to be larger or equal to 0.\nviolation(invalid_std) :-\n attribute((field,std),_,S),\n S < 0.\n\n% @hard(invalid_extent_order) Order has to be correct.\nviolation(invalid_extent_order) :-\n attribute((field,min),F,MIN),\n attribute((field,max),F,MAX),\n MIN > MAX.\n\n% @hard(invalid_non_string_freq) Frequency for strings only.\nviolation(invalid_non_string_freq) :-\n attribute((field,freq),F,_),\n not attribute((field,type),F,string).\n\n% @hard(enforce_order) property should follow natural order for generated entities.\nviolation(enforce_order):-\n entity(view,root,V), M1 < M2,\n attribute((mark,type),(V,M1),T1),\n attribute((mark,type),(V,M2),T2),\n not T1 < T2.\nviolation(enforce_order):-\n entity(mark,_,M), E1 < E2,\n attribute((encoding,channel),(M,E1),C1),\n attribute((encoding,channel),(M,E2),C2),\n not C1 < C2.\n" }, { "name": "soft", "src": "% ====== Preferences ======\n\n% @soft(aggregate) Prefer to use raw (no aggregate).\npreference(aggregate,E) :-\n attribute((encoding,aggregate),E,_).\n\n% @soft(bin) Prefer to not bin.\npreference(bin,E) :-\n attribute((encoding,binning),E,_).\npreference(bin,Fa) :-\n attribute((facet,binning),Fa,_).\n\n% @soft(bin_high) Prefer binning with at most 12 buckets.\npreference(bin_high,E) :-\n attribute((encoding,binning),E,B), B > 12.\n\n% @soft(bin_low) Prefer binning with more than 7 buckets.\npreference(bin_low,E) :-\n attribute((encoding,binning),E,B), B <= 7.\n\n% @soft(encoding) Prefer to use fewer encodings.\npreference(encoding,E) :-\n entity(encoding,_,E).\n\n% @soft(encoding_field) Prefer to use fewer encodings with fields (count does not have a field).\npreference(encoding_field,E) :-\n attribute((encoding,field),E,_).\n\n% @soft(facet_field) Prefer to use fewer facets with fields.\npreference(facet_field,F) :-\n attribute((facet,field),F,_).\n\n% @soft(facet_used_before_pos) Prefer not to use faceting until both x and y channels are used.\npreference(facet_used_before_pos,V) :-\n entity(facet,V,_),\n entity(mark,V,M),\n not helper((mark,channel),M,x).\npreference(facet_used_before_pos,V) :-\n entity(facet,V,_),\n entity(mark,V,M),\n not helper((mark,channel),M,y).\n\n% @soft(facet_used_before_color) Prefer not to use faceting until color channel is used.\npreference(facet_used_before_color,V) :-\n entity(facet,V,_),\n entity(mark,V,M),\n not helper((mark,channel),M,color).\n\n% @soft(facet_row) Prefer to use column facets over row facets.\npreference(facet_row,F) :-\n attribute((facet,channel),F,row).\n\n% @soft(facet_col) Prefer to use row facets over column facets.\npreference(facet_col,F) :-\n attribute((facet,channel),F,col).\n\n% @soft(same_field) Prefer not to use the same field twice for the same mark.\npreference(same_field,F) :-\n entity(field,_,F),\n entity(mark,_,M),\n { entity(encoding,M,E): helper((encoding,field),E,F) } = 2.\n\n% @soft(same_field_grt3) Prefer not to use the same field three or more times for the same mark.\npreference(same_field_grt3,F) :-\n entity(field,_,F),\n entity(mark,_,M),\n { entity(encoding,M,E): helper((encoding,field),E,F) } >= 3.\n\n% @soft(same_mark_type) Prefer not to use the same mark type more than once in the same view.\npreference(same_mark_type,V) :-\n entity(mark,V,M1),\n entity(mark,V,M2),\n M1 != M2,\n attribute((mark,type),M1,Type),\n attribute((mark,type),M2,Type).\n\n% @soft(same_channel) Prefer not to use the same channel more than once in the same view.\npreference(same_channel,V) :-\n entity(encoding,M1,E1),\n entity(encoding,M2,E2),\n entity(mark,V,M1),\n entity(mark,V,M2),\n E1 != E2,\n attribute((encoding,channel),E1,Channel),\n attribute((encoding,channel),E2,Channel).\n\n% @soft(count_grt1) Prefer not to use count more than once.\npreference(count_grt1,M) :-\n entity(mark,_,M),\n { entity(encoding,M,E): attribute((encoding,aggregate),E,count) } > 1.\n\n% @soft(number_categorical) Should not use categorical scale for number field.\npreference(number_categorical,E) :-\n attribute((field,type),F,number),\n helper((encoding,field),E,F),\n helper((encoding,scale_type),E,categorical).\n\n% @soft(bin_low_unique) Binned field should not have too less unique values.\npreference(bin_low_unique,E) :-\n attribute((field,type),F,(number;datetime)),\n attribute((field,unique),F,U),\n helper((encoding,field),E,F),\n attribute((encoding,binning),E,_),\n U < 40.\n\n% @soft(bin_not_linear) Prefer linear scale for bin.\npreference(bin_not_linear,E) :-\n attribute((encoding,binning),E,_),\n not helper((encoding,scale_type),E,linear).\n\n% @soft(bin_string) Prefer not to bin string fields.\npreference(bin_string,E) :-\n attribute((field,type),F,string),\n helper((encoding,field),E,F),\n attribute((encoding,binning),E,_).\npreference(bin_string,Fa) :-\n attribute((field,type),F,string),\n helper((facet,field),Fa,F),\n attribute((facet,binning),Fa,_).\n\n% @soft(only_discrete) Only discrete encoding channels are used in a mark.\npreference(only_discrete,M) :-\n entity(mark,_,M),\n not helper(mark_encoding_cont,M,_).\n\n% @soft(multi_non_pos) Prefer not to use multiple non-positional encoding channels.\npreference(multi_non_pos,M) :-\n entity(mark,_,M),\n { helper((mark,channel),M,C): domain(non_positional,C) } > 1.\n\n% @soft(non_pos_used_before_pos) Prefer not to use non-positional channels until all positional channels are used.\npreference(non_pos_used_before_pos,M) :-\n helper((mark,channel),M,C),\n domain(non_positional,C),\n not helper((mark,channel),M,(x;y)).\n\n% @soft(aggregate_group_by_raw) Aggregate plots should not use raw continuous as group by.\npreference(aggregate_group_by_raw,E) :-\n entity(encoding,M,EA),\n attribute((encoding,aggregate),EA,_),\n entity(encoding,M,E),\n not helper(mark_encoding_discrete_or_binned,M,E),\n not attribute((encoding,aggregate),E,_).\n\n% @soft(aggregate_no_discrete) Aggregate should also have a discrete encoding to group by.\npreference(aggregate_no_discrete,M) :-\n entity(encoding,M,EA),\n attribute((encoding,aggregate),EA,_),\n not helper(mark_encoding_discrete_or_binned,M,_).\n\n% @soft(x_y_raw) Prefer not to use plot with both x and y discrete and no aggregate as it leads to occlusion.\npreference(x_y_raw,M) :-\n helper(mark_channel_discrete_or_binned,M,x),\n helper(mark_channel_discrete_or_binned,M,y),\n entity(encoding,M,E),\n not helper(mark_encoding_discrete_or_binned,M,E),\n not attribute((encoding,aggregate),E,_).\n\n% @soft(continuous_not_zero) Prefer to include zero for continuous (binned doesn't need zero).\npreference(continuous_not_zero,E) :-\n not helper(mark_encoding_discrete_or_binned,M,E),\n helper(mark_encoding_scale,M,E,S),\n not attribute((scale,zero),S,true).\n\n% @soft(size_not_zero) Prefer zero size (even when binned).\npreference(size_not_zero,E) :-\n attribute((encoding,channel),E,size),\n helper(mark_encoding_scale,M,E,S),\n not attribute((scale,zero),S,true).\n\n% @soft(continuous_pos_not_zero) Prefer zero continuous positional.\npreference(continuous_pos_not_zero,E) :-\n attribute((encoding,channel),E,(x;y)),\n not helper(mark_encoding_discrete_or_binned,M,E),\n helper(mark_encoding_scale,M,E,S),\n not attribute((scale,zero),S,true).\n\n% @soft(skew_zero) Prefer not to use zero when the difference between min and max is smaller than distance to 0.\npreference(skew_zero,E) :-\n attribute((field,min),F,MIN),\n attribute((field,max),F,MAX),\n EXT = MAX - MIN,\n |MAX| > EXT,\n |MIN| > EXT,\n entity(encoding,M,E),\n helper((encoding,field),E,F),\n helper(mark_encoding_scale,M,E,S),\n attribute((scale,zero),S,true).\n\n% @soft(cross_zero) Prefer not to include zero as baseline when the range of data crosses zero.\npreference(cross_zero,E) :-\n attribute((field,min),F,MIN),\n attribute((field,max),F,MAX),\n MAX > 0,\n MIN < 0,\n entity(encoding,M,E),\n helper((encoding,field),E,F),\n helper(mark_encoding_scale,M,E,S),\n attribute((scale,zero),S,true).\n\n% @soft(only_y) Prefer to use only x instead of only y.\npreference(only_y,M) :-\n helper((mark,channel),M,y),\n not helper((mark,channel),M,x).\n\n% @soft(binned_orientation_not_x) Prefer binned quantitative on x axis.\npreference(binned_orientation_not_x,E) :-\n attribute((field,type),F,(number;datetime)),\n helper((encoding,field),E,F),\n attribute((encoding,binning),E,_),\n not attribute((encoding,channel),_,x).\n\n% @soft(high_cardinality_ordinal) Prefer not to use ordinal for fields with high cardinality.\npreference(high_cardinality_ordinal,E) :-\n helper(encoding_cardinality,E,N),\n helper((encoding,scale_type),E,ordinal),\n N > 30.\n\n% @soft(high_cardinality_categorical_grt10) Prefer not to use categorical (color) for fields with high cardinality.\npreference(high_cardinality_categorical_grt10,E) :-\n helper(encoding_cardinality,E,N),\n helper((encoding,scale_type),E,categorical),\n N > 10.\n\n% @soft(high_cardinality_categorical_line_point_color) Prefer faceting over color for line/point marks with high cardinality categorical data to avoid overplotting.\npreference(high_cardinality_categorical_line_point_color,E) :-\n entity(encoding,M,E),\n attribute((mark,type),M,(line;point)),\n attribute((encoding,channel),E,color),\n helper((encoding,field),E,F),\n helper((encoding,scale_type),E,categorical),\n helper(encoding_cardinality,E,N),\n N > 4.\n\n% @soft(high_cardinality_point_color_before_facet) Prefer faceting on both row and col before color for point marks with high cardinality to avoid overplotting.\npreference(high_cardinality_point_color_before_facet,V) :-\n entity(mark,V,M),\n attribute((mark,type),M,point),\n entity(encoding,M,E),\n helper(encoding_cardinality,E,N),\n N > 75,\n helper((mark,channel),M,color),\n not entity(facet,V,_).\npreference(high_cardinality_point_color_before_facet,V) :-\n entity(mark,V,M),\n attribute((mark,type),M,point),\n entity(encoding,M,E),\n helper(encoding_cardinality,E,N),\n N > 75,\n helper((mark,channel),M,color),\n entity(facet,V,F1),\n attribute((facet,channel),F1,row),\n not entity(facet,V,F2) : attribute((facet,channel),F2,col).\npreference(high_cardinality_point_color_before_facet,V) :-\n entity(mark,V,M),\n attribute((mark,type),M,point),\n entity(encoding,M,E),\n helper(encoding_cardinality,E,N),\n N > 75,\n helper((mark,channel),M,color),\n entity(facet,V,F1),\n attribute((facet,channel),F1,col),\n not entity(facet,V,F2) : attribute((facet,channel),F2,row).\n\n% @soft(high_cardinality_categorical_ordinal_color) Prefer not to use ordinal scale with color for categorical data with high cardinality.\npreference(high_cardinality_categorical_ordinal_color,E) :-\n attribute((encoding,channel),E,color),\n helper((encoding,field),E,F),\n attribute((field,type),F,string),\n helper((encoding,scale_type),E,ordinal),\n helper(encoding_cardinality,E,N),\n N > 4.\n\n% @soft(high_cardinality_shape) Prefer not to use high cardinality ordinal for shape.\npreference(high_cardinality_shape,E) :-\n helper(encoding_cardinality,E,N),\n attribute((encoding,channel),E,shape),\n N > 8.\n\n% @soft(high_cardinality_size) Prefer not to use size when the cardinality is large on x or y.\npreference(high_cardinality_size,E) :-\n helper((mark,channel),M,size),\n entity(encoding,M,E),\n helper(encoding_cardinality,E,N),\n attribute((encoding,channel),E,(x;y)),\n not helper(mark_encoding_discrete_or_binned,M,E),\n N > 100.\n\n% @soft(high_cardinality_facet) Prefer not to use faceting on row and col when field cardinality is large.\npreference(high_cardinality_facet,F) :-\n attribute((facet,channel),F,(row;col)),\n helper(facet_cardinality,F,N),\n N > 12.\n\n% @soft(underplotting) Prefer not to facet when it results in too few data points per facet.\npreference(underplotting,V) :-\n entity(facet,V,_),\n attribute(number_rows,root,NR),\n helper(total_facet_cardinality,V,NF),\n NR < 3 * NF.\n\n% @soft(horizontal_scrolling_x) Avoid high cardinality on x as it causes horizontal scrolling.\npreference(horizontal_scrolling_x,E) :-\n attribute((encoding,channel),E,x),\n helper(encoding_cardinality,E,N),\n helper(mark_encoding_discrete_or_binned,_,E),\n N > 50.\n\n% @soft(horizontal_scrolling_col) Avoid high cardinality on column as it causes horizontal scrolling.\npreference(horizontal_scrolling_col,F) :-\n attribute((facet,channel),F,col),\n helper(facet_cardinality,F,N),\n N > 5.\n\n% @soft(vertical_scrolling_y) Avoid high cardinality on y as it causes vertical scrolling.\npreference(vertical_scrolling_y,E) :-\n attribute((encoding,channel),E,y),\n helper(encoding_cardinality,E,N),\n helper(mark_encoding_discrete_or_binned,_,E),\n N > 50.\n\n% @soft(vertical_scrolling_row) Avoid high cardinality on row as it causes vertical scrolling.\npreference(vertical_scrolling_row,F) :-\n attribute((facet,channel),F,row),\n helper(facet_cardinality,F,N),\n N > 5.\n\n% @soft(date_scale) Prefer to use linear/ordinal scale type with dates.\npreference(date_scale,E) :-\n attribute((field,type),F,datetime),\n helper((encoding,field),E,F),\n not helper((encoding,scale_type),E,linear),\n not helper((encoding,scale_type),E,ordinal).\n\n% @soft(number_linear) Prefer use linear for numbers with high cardinality.\npreference(number_linear,E) :-\n attribute((field,type),F,number),\n attribute((field,unique),F,N),\n N > 20,\n helper((encoding,field),E,F),\n not attribute((encoding,binning),E,_),\n not helper((encoding,scale_type),E,linear).\n\n% @soft(position_entropy) Overplotting. Prefer not to use x and y for continuous with high cardinality and low entropy without aggregation because the points will overplot.\npreference(position_entropy,E) :-\n attribute((encoding,channel),E,(x;y)),\n attribute(encoding_cardinality,E,N),\n N > 100,\n attribute((field,entropy),F,EN),\n helper((encoding,field),E,F),\n EN <= 3000,\n not helper(mark_encoding_discrete_or_binned,_,E),\n not attribute((encoding,aggregate),E,_).\n\n% @soft(value_agg) Prefer not to aggregate for value tasks.\npreference(value_agg,V) :-\n attribute(task,root,value),\n entity(mark,V,M),\n entity(encoding,M,E),\n attribute((encoding,aggregate),E,_).\n\n% @soft(value_bin) Prefer not to bin for value tasks as it reduces detail needed for value lookup.\npreference(value_bin,E) :-\n attribute(task,root,value),\n attribute((encoding,binning),E,_).\npreference(value_bin,Fa) :-\n attribute(task,root,value),\n attribute((facet,binning),Fa,_).\n\n% @soft(summary_facet) Prefer not to use facet for summary tasks as it makes it difficult to compare.\npreference(summary_facet,V) :-\n attribute(task,root,summary),\n entity(facet,V,_).\n\n% @soft(c_d_col) Prefer not to use continuous on x, discrete on y, and column.\npreference(c_d_col,V) :-\n entity(mark,V,M),\n not helper(mark_channel_discrete_or_binned,M,x),\n helper(mark_channel_discrete_or_binned,M,y),\n entity(facet,V,F),\n attribute((facet,channel),F,col).\n\n% @soft(date_not_x) Prefer datetime on x.\npreference(date_not_x,E) :-\n attribute((field,type),F,datetime),\n helper((encoding,field),E,F),\n not attribute((encoding,channel),E,x).\n\n% @soft(date_not_line) Prefer line mark when using datetime field.\npreference(date_not_line,M) :-\n entity(encoding,M,E),\n helper((encoding,field),E,F),\n attribute((field,type),F,datetime),\n not attribute((mark,type),M,line).\n\n% @soft(x_row) Positional interactions as suggested by Kim et al.\npreference(x_row,V) :-\n entity(mark,V,M),\n helper((mark,channel),M,x),\n entity(facet,V,F),\n attribute((facet,channel),F,row).\n\n% @soft(y_row) Positional interactions as suggested by Kim et al.\npreference(y_row,V) :-\n entity(mark,V,M),\n helper((mark,channel),M,y),\n entity(facet,V,F),\n attribute((facet,channel),F,row).\n\n% @soft(x_col) Positional interactions as suggested by Kim et al.\npreference(x_col,V) :-\n entity(mark,V,M),\n helper((mark,channel),M,x),\n entity(facet,V,F),\n attribute((facet,channel),F,col).\n\n% @soft(y_col) Positional interactions as suggested by Kim et al.\npreference(y_col,V) :-\n entity(mark,V,M),\n helper((mark,channel),M,y),\n entity(facet,V,F),\n attribute((facet,channel),F,col).\n\n% @soft(color_entropy_high) Entropy, primary quantive interactions as suggested by Kim et al.\npreference(color_entropy_high,E) :-\n attribute((field,entropy),F,EN),\n helper((encoding,field),E,F),\n attribute((encoding,channel),E,color),\n helper((encoding,scale_type),E,linear),\n helper(enc_interesting,E),\n EN > 3000.\n\n% @soft(color_entropy_low) Entropy, primary quantive interactions as suggested by Kim et al.\npreference(color_entropy_low,E) :-\n attribute((field,entropy),F,EN),\n helper((encoding,field),E,F),\n attribute((encoding,channel),E,color),\n helper((encoding,scale_type),E,linear),\n helper(enc_interesting,E),\n EN <= 3000.\n\n% @soft(size_entropy_high) Entropy, primary quantive interactions as suggested by Kim et al.\npreference(size_entropy_high,E) :-\n attribute((field,entropy),F,EN),\n helper((encoding,field),E,F),\n attribute((encoding,channel),E,size),\n helper((encoding,scale_type),E,linear),\n helper(enc_interesting,E),\n EN > 3000.\n\n% @soft(size_entropy_low) Entropy, primary quantive interactions as suggested by Kim et al.\npreference(size_entropy_low,E) :-\n attribute((field,entropy),F,EN),\n helper((encoding,field),E,F),\n attribute((encoding,channel),E,size),\n helper((encoding,scale_type),E,linear),\n helper(enc_interesting,E),\n EN <= 3000.\n\n% @soft(linear_scale) linear scale.\npreference(linear_scale,E) :-\n helper((encoding,scale_type),E,linear).\n\n% @soft(log_scale) log scale.\npreference(log_scale,E) :-\n helper((encoding,scale_type),E,log).\n\n% @soft(ordinal_scale) ordinal scale.\npreference(ordinal_scale,E) :-\n helper((encoding,scale_type),E,ordinal).\n\n% @soft(categorical_scale) categorical scale.\npreference(categorical_scale,E) :-\n helper((encoding,scale_type),E,categorical).\n\n% @soft(continuous_scale) Prefer to use linear, log or symlog scale for continuous variables.\npreference(continuous_scale,E) :-\n helper((encoding,field),E,F),\n attribute((field,type),F,(number;datetime)),\n not helper((encoding,scale_type),E,linear),\n not helper((encoding,scale_type),E,log),\n not helper((encoding,scale_type),E,symlog).\n\n% @soft(skewed_not_log) Prefer log scale for positive skewed fields and symlog for fields that cross zero.\npreference(skewed_not_log,E) :-\n helper((encoding,field),E,F),\n helper(field_skewed,F),\n attribute((field,min),F,MIN),\n MIN > 0,\n not helper((encoding,scale_type),E,log).\npreference(skewed_not_log,E) :-\n helper((encoding,field),E,F),\n helper(field_skewed,F),\n attribute((field,min),F,MIN),\n attribute((field,max),F,MAX),\n MIN < 1,\n MAX > 0,\n not helper((encoding,scale_type),E,symlog).\n\n% @soft(c_c_point) Continuous by continuous for point mark.\npreference(c_c_point,M) :-\n helper(is_c_c,M),\n attribute((mark,type),M,point).\n\n% @soft(c_c_line) Continuous by continuous for line mark.\npreference(c_c_line,M) :-\n helper(is_c_c,M),\n attribute((mark,type),M,line).\n\n% @soft(c_c_area) Continuous by continuous for area mark.\npreference(c_c_area,M) :-\n helper(is_c_c,M),\n attribute((mark,type),M,area).\n\n% @soft(c_c_text) Continuous by continuous for text mark.\npreference(c_c_text,M) :-\n helper(is_c_c,M),\n attribute((mark,type),M,text).\n\n% @soft(c_d_overlap_point) Continuous by discrete for point mark.\npreference(c_d_overlap_point,M) :-\n helper(is_c_d,M),\n not helper(no_overlap,M),\n attribute((mark,type),M,point).\n\n% @soft(c_d_overlap_bar) Continuous by discrete for bar mark.\npreference(c_d_overlap_bar,M) :-\n helper(is_c_d,M),\n not helper(no_overlap,M),\n attribute((mark,type),M,bar).\n\n% @soft(c_d_overlap_line) Continuous by discrete for line mark.\npreference(c_d_overlap_line,M) :-\n helper(is_c_d,M),\n not helper(no_overlap,M),\n attribute((mark,type),M,line).\n\n% @soft(c_d_overlap_area) Continuous by discrete for area mark.\npreference(c_d_overlap_area,M) :-\n helper(is_c_d,M),\n not helper(no_overlap,M),\n attribute((mark,type),M,area).\n\n% @soft(c_d_overlap_text) Continuous by discrete for text mark.\npreference(c_d_overlap_text,M) :-\n helper(is_c_d,M),\n not helper(no_overlap,M),\n attribute((mark,type),M,text).\n\n% @soft(c_d_overlap_tick) Continuous by discrete for tick mark.\npreference(c_d_overlap_tick,M) :-\n helper(is_c_d,M),\n not helper(no_overlap,M),\n attribute((mark,type),M,tick).\n\n% @soft(c_d_no_overlap_point) Continuous by discrete for point mark.\npreference(c_d_no_overlap_point,M) :-\n helper(is_c_d,M),\n helper(no_overlap,M),\n attribute((mark,type),M,point).\n\n% @soft(c_d_no_overlap_bar) Continuous by discrete for bar mark.\npreference(c_d_no_overlap_bar,M) :-\n helper(is_c_d,M),\n helper(no_overlap,M),\n attribute((mark,type),M,bar).\n\n% @soft(c_d_no_overlap_line) Continuous by discrete for line mark.\npreference(c_d_no_overlap_line,M) :-\n helper(is_c_d,M),\n helper(no_overlap,M),\n attribute((mark,type),M,line).\n\n% @soft(c_d_no_overlap_area) Continuous by discrete for area mark.\npreference(c_d_no_overlap_area,M) :-\n helper(is_c_d,M),\n helper(no_overlap,M),\n attribute((mark,type),M,area).\n\n% @soft(c_d_no_overlap_text) Continuous by discrete for text mark.\npreference(c_d_no_overlap_text,M) :-\n helper(is_c_d,M),\n helper(no_overlap,M),\n attribute((mark,type),M,text).\n\n% @soft(c_d_no_overlap_tick) Continuous by discrete for tick mark.\npreference(c_d_no_overlap_tick,M) :-\n helper(is_c_d,M),\n helper(no_overlap,M),\n attribute((mark,type),M,tick).\n\n% @soft(tick_color) Prefer not to use color with tick marks as it is difficult to see.\npreference(tick_color,M) :-\n attribute((mark,type),M,tick),\n helper((mark,channel),M,color).\n\n% @soft(d_d_overlap) Prefer not to overlap with DxD.\npreference(d_d_overlap,M) :-\n helper(is_d_d,M),\n helper(overlap,M).\n\n% @soft(d_d_point) Discrete by discrete for point mark.\npreference(d_d_point,M) :-\n helper(is_d_d,M),\n attribute((mark,type),M,point).\n\n% @soft(d_d_text) Discrete by discrete for text mark.\npreference(d_d_text,M) :-\n helper(is_d_d,M),\n attribute((mark,type),M,text).\n\n% @soft(d_d_rect) Discrete by discrete for rect mark.\npreference(d_d_rect,M) :-\n helper(is_d_d,M),\n attribute((mark,type),M,rect).\n\n% @soft(linear_x) Linear scale with x channel.\npreference(linear_x,E) :-\n attribute((encoding,channel),E,x),\n helper((encoding,scale_type),E,linear).\n\n% @soft(linear_y) Linear scale with y channel.\npreference(linear_y,E) :-\n attribute((encoding,channel),E,y),\n helper((encoding,scale_type),E,linear).\n\n% @soft(linear_color) Linear scale with color channel.\npreference(linear_color,E) :-\n attribute((encoding,channel),E,color),\n helper((encoding,scale_type),E,linear).\n\n% @soft(linear_size) Linear scale with size channel.\npreference(linear_size,E) :-\n attribute((encoding,channel),E,size),\n helper((encoding,scale_type),E,linear).\n\n% @soft(linear_text) Linear scale with text channel.\npreference(linear_text,E) :-\n attribute((encoding,channel),E,text),\n helper((encoding,scale_type),E,linear).\n\n% @soft(log_x) Log scale with x channel.\npreference(log_x,E) :-\n attribute((encoding,channel),E,x),\n helper((encoding,scale_type),E,log).\n\n% @soft(log_y) Log scale with y channel.\npreference(log_y,E) :-\n attribute((encoding,channel),E,y),\n helper((encoding,scale_type),E,log).\n\n% @soft(log_color) Log scale with color channel.\npreference(log_color,E) :-\n attribute((encoding,channel),E,color),\n helper((encoding,scale_type),E,log).\n\n% @soft(log_size) Log scale with size channel.\npreference(log_size,E) :-\n attribute((encoding,channel),E,size),\n helper((encoding,scale_type),E,log).\n\n% @soft(log_text) Log scale with text channel.\npreference(log_text,E) :-\n attribute((encoding,channel),E,text),\n helper((encoding,scale_type),E,log).\n\n% @soft(ordinal_x) Ordinal scale with x channel.\npreference(ordinal_x,E) :-\n attribute((encoding,channel),E,x),\n helper((encoding,scale_type),E,ordinal).\n\n% @soft(ordinal_y) Ordinal scale with y channel.\npreference(ordinal_y,E) :-\n attribute((encoding,channel),E,y),\n helper((encoding,scale_type),E,ordinal).\n\n% @soft(ordinal_color) Ordinal scale with color channel.\npreference(ordinal_color,E) :-\n attribute((encoding,channel),E,color),\n helper((encoding,scale_type),E,ordinal).\n\n% @soft(ordinal_size) Ordinal scale with size channel.\npreference(ordinal_size,E) :-\n attribute((encoding,channel),E,size),\n helper((encoding,scale_type),E,ordinal).\n\n% @soft(ordinal_shape) Ordinal scale with shape channel.\npreference(ordinal_shape,E) :-\n attribute((encoding,channel),E,shape),\n helper((encoding,scale_type),E,ordinal).\n\n% @soft(ordinal_text) Ordinal scale with text channel.\npreference(ordinal_text,E) :-\n attribute((encoding,channel),E,text),\n helper((encoding,scale_type),E,ordinal).\n\n% @soft(ordinal_detail) Ordinal scale with detail channel.\npreference(ordinal_detail,E) :-\n attribute((encoding,channel),E,detail),\n helper((encoding,scale_type),E,ordinal).\n\n% @soft(categorical_color) Categorical scale with color channel.\npreference(categorical_color,E) :-\n attribute((encoding,channel),E,color),\n helper((encoding,scale_type),E,categorical).\n\n% @soft(aggregate_count) Count as aggregate op.\npreference(aggregate_count,E) :-\n attribute((encoding,aggregate),E,count).\n\n% @soft(aggregate_mean) Mean as aggregate op.\npreference(aggregate_mean,E) :-\n attribute((encoding,aggregate),E,mean).\n\n% @soft(aggregate_median) Median as aggregate op.\npreference(aggregate_median,E) :-\n attribute((encoding,aggregate),E,median).\n\n% @soft(aggregate_min) Min as aggregate op.\npreference(aggregate_min,E) :-\n attribute((encoding,aggregate),E,min).\n\n% @soft(aggregate_max) Max as aggregate op.\npreference(aggregate_max,E) :-\n attribute((encoding,aggregate),E,max).\n\n% @soft(aggregate_stdev) Stdev as aggregate op.\npreference(aggregate_stdev,E) :-\n attribute((encoding,aggregate),E,stdev).\n\n% @soft(aggregate_sum) Sum as aggregate op.\npreference(aggregate_sum,E) :-\n attribute((encoding,aggregate),E,sum).\n\n% @soft(stack_zero) Zero base for stack op.\npreference(stack_zero,E) :-\n attribute((encoding,stack),E,zero).\n\n% @soft(stack_center) Center groupbys as stack op.\npreference(stack_center,E) :-\n attribute((encoding,stack),E,center).\n\n% @soft(stack_normalize) Normalize between groupbys as stack op.\npreference(stack_normalize,E) :-\n attribute((encoding,stack),E,normalize).\n\n% @soft(value_point) Point mark for value tasks.\npreference(value_point,M) :-\n attribute(task,root,value),\n attribute((mark,type),M,point).\n\n% @soft(value_bar) Bar mark for value tasks.\npreference(value_bar,M) :-\n attribute(task,root,value),\n attribute((mark,type),M,bar).\n\n% @soft(value_line) Line mark for value tasks.\npreference(value_line,M) :-\n attribute(task,root,value),\n attribute((mark,type),M,line).\n\n% @soft(value_area) Area mark for value tasks.\npreference(value_area,M) :-\n attribute(task,root,value),\n attribute((mark,type),M,area).\n\n% @soft(value_text) Text mark for value tasks.\npreference(value_text,M) :-\n attribute(task,root,value),\n attribute((mark,type),M,text).\n\n% @soft(value_tick) Tick mark for value tasks.\npreference(value_tick,M) :-\n attribute(task,root,value),\n attribute((mark,type),M,tick).\n\n% @soft(value_rect) Rect mark for value tasks.\npreference(value_rect,M) :-\n attribute(task,root,value),\n attribute((mark,type),M,rect).\n\n% @soft(summary_point) Point mark for summary tasks.\npreference(summary_point,M) :-\n attribute(task,root,summary),\n attribute((mark,type),M,point).\n\n% @soft(summary_bar) Bar mark for summary tasks.\npreference(summary_bar,M) :-\n attribute(task,root,summary),\n attribute((mark,type),M,bar).\n\n% @soft(summary_line) Line mark for summary tasks.\npreference(summary_line,M) :-\n attribute(task,root,summary),\n attribute((mark,type),M,line).\n\n% @soft(summary_area) Area mark for summary tasks.\npreference(summary_area,M) :-\n attribute(task,root,summary),\n attribute((mark,type),M,area).\n\n% @soft(summary_text) Text mark for summary tasks.\npreference(summary_text,M) :-\n attribute(task,root,summary),\n attribute((mark,type),M,text).\n\n% @soft(summary_tick) Tick mark for summary tasks.\npreference(summary_tick,M) :-\n attribute(task,root,summary),\n attribute((mark,type),M,tick).\n\n% @soft(summary_rect) Rect mark for summary tasks.\npreference(summary_rect,M) :-\n attribute(task,root,summary),\n attribute((mark,type),M,rect).\n\n% @soft(value_continuous_x) Continuous x for value tasks.\npreference(value_continuous_x,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,x),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_continuous_y) Continuous y for value tasks.\npreference(value_continuous_y,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,y),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_continuous_color) Continuous color for value tasks.\npreference(value_continuous_color,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,color),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_continuous_size) Continuous size for value tasks.\npreference(value_continuous_size,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,size),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_continuous_text) Continuous text for value tasks.\npreference(value_continuous_text,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,text),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_discrete_x) Discrete x for value tasks.\npreference(value_discrete_x,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,x),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_discrete_y) Discrete y for value tasks.\npreference(value_discrete_y,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,y),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_discrete_color) Discrete color for value tasks.\npreference(value_discrete_color,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,color),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_discrete_size) Discrete size for value tasks.\npreference(value_discrete_size,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,size),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_discrete_shape) Discrete shape for value tasks.\npreference(value_discrete_shape,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,shape),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_discrete_text) Discrete text for value tasks.\npreference(value_discrete_text,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,text),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(value_discrete_detail) Discrete detail for value tasks.\npreference(value_discrete_detail,E) :-\n attribute(task,root,value),\n attribute((encoding,channel),E,detail),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_continuous_x) Continuous x for summary tasks.\npreference(summary_continuous_x,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,x),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_continuous_y) Continuous y for summary tasks.\npreference(summary_continuous_y,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,y),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_continuous_color) Continuous color for summary tasks.\npreference(summary_continuous_color,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,color),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_continuous_size) Continuous size for summary tasks.\npreference(summary_continuous_size,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,size),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_continuous_text) Continuous text for summary tasks.\npreference(summary_continuous_text,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,text),\n not helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_discrete_x) Discrete x for summary tasks.\npreference(summary_discrete_x,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,x),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_discrete_y) Discrete y for summary tasks.\npreference(summary_discrete_y,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,y),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_discrete_color) Discrete color for summary tasks.\npreference(summary_discrete_color,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,color),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_discrete_size) Discrete size for summary tasks.\npreference(summary_discrete_size,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,size),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_discrete_shape) Discrete shape for summary tasks.\npreference(summary_discrete_shape,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,shape),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_discrete_text) Discrete text for summary tasks.\npreference(summary_discrete_text,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,text),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(summary_discrete_detail) Discrete detail for summary tasks.\npreference(summary_discrete_detail,E) :-\n attribute(task,root,summary),\n attribute((encoding,channel),E,detail),\n helper(mark_encoding_discrete_or_binned,_,E),\n helper(enc_interesting,E).\n\n% @soft(interesting_x) Interesting on x channel.\npreference(interesting_x,E) :-\n attribute((encoding,channel),E,x),\n helper(enc_interesting,E).\n\n% @soft(interesting_y) Interesting on y channel.\npreference(interesting_y,E) :-\n attribute((encoding,channel),E,y),\n helper(enc_interesting,E).\n\n% @soft(interesting_color) Interesting on color channel.\npreference(interesting_color,E) :-\n attribute((encoding,channel),E,color),\n helper(enc_interesting,E).\n\n% @soft(interesting_size) Interesting on size channel.\npreference(interesting_size,E) :-\n attribute((encoding,channel),E,size),\n helper(enc_interesting,E).\n\n% @soft(interesting_shape) Interesting on shape channel.\npreference(interesting_shape,E) :-\n attribute((encoding,channel),E,shape),\n helper(enc_interesting,E).\n\n% @soft(interesting_text) Interesting on text channel.\npreference(interesting_text,E) :-\n attribute((encoding,channel),E,text),\n helper(enc_interesting,E).\n\n% @soft(interesting_row) Interesting on row channel.\npreference(interesting_row,E) :-\n attribute((facet,channel),E,row),\n helper(enc_interesting,E).\n\n% @soft(interesting_column) Interesting on column channel.\npreference(interesting_column,E) :-\n attribute((facet,channel),E,col),\n helper(enc_interesting,E).\n\n% @soft(interesting_detail) Interesting on detail channel.\npreference(interesting_detail,E) :-\n attribute((encoding,channel),E,detail),\n helper(enc_interesting,E).\n\n% @soft(cartesian_coordinate) Cartesian coordinates.\npreference(cartesian_coordinate,V) :-\n attribute((view,coordinates),V,cartesian).\n\n% @soft(polar_coordinate) Polar coordinates.\npreference(polar_coordinate,V) :-\n attribute((view,coordinates),V,polar).\n" }, { "name": "optimize", "src": "% Minimize the feature weight\n\n#minimize { W,F,Q: preference_weight(F,W), preference(F,Q) }.\n" }, { "name": "express", "src": "% ====== Field Control ======\n\n% @api(require(field, fieldname)) Guarantee that a field is visualized via encoding or facet\nviolation(field_not_visualized) :-\n require(field, FieldName),\n not attribute((encoding,field),_,FieldName),\n not attribute((facet,field),_,FieldName).\n\n% @api(require((field, encoding), fieldname)) Guarantee that a specific field is used in an encoding\nviolation(field_not_in_encoding) :-\n require((field, encoding), FieldName),\n not attribute((encoding,field),_,FieldName).\n\n% @api(require((field, facet), fieldname)) Guarantee that a specific field is used in a facet\nviolation(field_not_in_facet) :-\n require((field, facet), FieldName),\n not attribute((facet,field),_,FieldName).\n\n% @api(forbid(field, fieldname)) Forbid a field from being used in any visualization\nviolation(forbidden_field_used) :-\n forbid(field, FieldName),\n attribute((encoding,field),_,FieldName).\n\nviolation(forbidden_field_used) :-\n forbid(field, FieldName),\n attribute((facet,field),_,FieldName).\n\n% @api(observed(field, fieldname)) Field is used (in encoding or facet)\nobserved(field, FieldName) :-\n attribute((encoding,field),_,FieldName).\n\nobserved(field, FieldName) :-\n attribute((facet,field),_,FieldName).\n\n% @api(observed((field, encoding), fieldname)) Field is used in an encoding\nobserved((field, encoding), FieldName) :-\n attribute((encoding,field),_,FieldName).\n\n% @api(observed((field, facet), fieldname)) Field is used in a facet\nobserved((field, facet), FieldName) :-\n attribute((facet,field),_,FieldName).\n\n% ====== Mark Control ======\n\n% @api(require(mark, marktype)) Guarantee that a specific mark type is used\nviolation(required_mark_missing) :-\n require(mark, MarkType),\n not attribute((mark,type),_,MarkType).\n\n% @api(forbid(mark, marktype)) Forbid a specific mark type from being used\nviolation(forbidden_mark_used) :-\n forbid(mark, MarkType),\n attribute((mark,type),_,MarkType).\n\n% @api(observed(mark, marktype)) Mark type is used\nobserved(mark, MarkType) :-\n attribute((mark,type),_,MarkType).\n\n% ====== Channel Control ======\n\n% @api(require(channel, channelname)) Guarantee that a specific channel is used\nviolation(required_channel_missing) :-\n require(channel, Channel),\n not attribute((encoding,channel),_,Channel).\n\n% @api(forbid(channel, channelname)) Forbid a specific channel from being used\nviolation(forbidden_channel_used) :-\n forbid(channel, Channel),\n attribute((encoding,channel),_,Channel).\n\n% @api(observed(channel, channelname)) Channel is used\nobserved(channel, Channel) :-\n attribute((encoding,channel),_,Channel).\n\n% ====== Facet Control ======\n\n% @api(require(facet)) Guarantee that faceting is used\nviolation(faceting_missing) :-\n require(facet),\n not entity(facet,_,_).\n\n% @api(forbid(facet)) Forbid any faceting\nviolation(faceting_forbidden) :-\n forbid(facet),\n entity(facet,_,_).\n\n% @api(require((facet, channel), channelname)) Require faceting on a specific channel (row/col)\nviolation(facet_channel_missing) :-\n require((facet, channel), Channel),\n not attribute((facet,channel),_,Channel).\n\n% @api(forbid((facet, channel), channelname)) Forbid faceting on a specific channel\nviolation(facet_channel_forbidden) :-\n forbid((facet, channel), Channel),\n attribute((facet,channel),_,Channel).\n\n% @api(observed(facet)) Faceting is used\nobserved(facet) :-\n entity(facet,_,_).\n\n% @api(observed((facet, channel), channelname)) Specific facet channel is used\nobserved((facet, channel), Channel) :-\n attribute((facet,channel),_,Channel).\n\n% ====== Binning Control ======\n\n% @api(require(binning)) Guarantee that at least one encoding uses binning\nviolation(binning_missing) :-\n require(binning),\n not attribute((encoding,binning),_,_).\n\n% @api(forbid(binning)) Forbid any binning\nviolation(binning_forbidden) :-\n forbid(binning),\n attribute((encoding,binning),_,_).\n\n% @api(require(binning, fieldname)) Require a specific field to be binned\nviolation(field_not_binned) :-\n require(binning, FieldName),\n attribute((encoding,field),E,FieldName),\n not attribute((encoding,binning),E,_).\n\n% @api(forbid(binning, fieldname)) Forbid a specific field from being binned\nviolation(field_binned_forbidden) :-\n forbid(binning, FieldName),\n attribute((encoding,field),E,FieldName),\n attribute((encoding,binning),E,_).\n\n% @api(observed(binning)) Binning is used\nobserved(binning) :-\n attribute((encoding,binning),_,_).\n\n% @api(observed(binning, fieldname)) Specific field is binned\nobserved(binning, FieldName) :-\n attribute((encoding,field),E,FieldName),\n attribute((encoding,binning),E,_).\n\n% ====== Aggregation Control ======\n\n% @api(require(aggregate)) Guarantee that at least one encoding uses aggregation\nviolation(aggregation_missing) :-\n require(aggregate),\n not attribute((encoding,aggregate),_,_).\n\n% @api(forbid(aggregate)) Forbid any aggregation\nviolation(aggregation_forbidden) :-\n forbid(aggregate),\n attribute((encoding,aggregate),_,_).\n\n% @api(require(aggregate, aggtype)) Require a specific aggregation type to be used\nviolation(required_aggregate_missing) :-\n require(aggregate, AggType),\n not attribute((encoding,aggregate),_,AggType).\n\n% @api(forbid(aggregate, aggtype)) Forbid a specific aggregation type\nviolation(forbidden_aggregate_used) :-\n forbid(aggregate, AggType),\n attribute((encoding,aggregate),_,AggType).\n\n% @api(observed(aggregate)) Aggregation is used\nobserved(aggregate) :-\n attribute((encoding,aggregate),_,_).\n\n% @api(observed(aggregate, aggtype)) Specific aggregation type is used\nobserved(aggregate, AggType) :-\n attribute((encoding,aggregate),_,AggType).\n\n% ====== Scale Control ======\n\n% @api(require(scale, scaletype)) Require a specific scale type to be used\nviolation(required_scale_missing) :-\n require(scale, ScaleType),\n not attribute((scale,type),_,ScaleType).\n\n% @api(forbid(scale, scaletype)) Forbid a specific scale type\nviolation(forbidden_scale_used) :-\n forbid(scale, ScaleType),\n attribute((scale,type),_,ScaleType).\n\n% @api(require((scale, channel), scaletype)) Require a specific scale type for a specific channel\nviolation(required_channel_scale_missing) :-\n require((scale, Channel), ScaleType),\n entity(scale,_,S),\n attribute((scale,channel),S,Channel),\n not attribute((scale,type),S,ScaleType).\n\n% @api(forbid((scale, channel), scaletype)) Forbid a specific scale type for a specific channel\nviolation(forbidden_channel_scale_used) :-\n forbid((scale, Channel), ScaleType),\n entity(scale,_,S),\n attribute((scale,channel),S,Channel),\n attribute((scale,type),S,ScaleType).\n\n% @api(observed(scale, scaletype)) Scale type is used\nobserved(scale, ScaleType) :-\n attribute((scale,type),_,ScaleType).\n\n% @api(observed((scale, channel), scaletype)) Specific scale type used on specific channel\nobserved((scale, Channel), ScaleType) :-\n entity(scale,_,S),\n attribute((scale,channel),S,Channel),\n attribute((scale,type),S,ScaleType).\n\n% ====== Stack Control ======\n\n% @api(require(stack)) Guarantee that stacking is used\nviolation(stacking_missing) :-\n require(stack),\n not attribute((encoding,stack),_,_).\n\n% @api(forbid(stack)) Forbid any stacking\nviolation(stacking_forbidden) :-\n forbid(stack),\n attribute((encoding,stack),_,_).\n\n% @api(require(stack, stackmethod)) Require a specific stacking method\nviolation(required_stack_method_missing) :-\n require(stack, StackMethod),\n not attribute((encoding,stack),_,StackMethod).\n\n% @api(forbid(stack, stackmethod)) Forbid a specific stacking method\nviolation(forbidden_stack_method_used) :-\n forbid(stack, StackMethod),\n attribute((encoding,stack),_,StackMethod).\n\n% @api(observed(stack)) Stacking is used\nobserved(stack) :-\n attribute((encoding,stack),_,_).\n\n% @api(observed(stack, stackmethod)) Specific stack method is used\nobserved(stack, StackMethod) :-\n attribute((encoding,stack),_,StackMethod).\n\n% ====== Definition Declarations ======\n% Tell Clingo these predicates are defined externally to silence \"atom does not occur in any rule head\" warnings.\n\n#defined require/1.\n#defined require/2.\n#defined forbid/1.\n#defined forbid/2.\n" }, { "name": "spec", "src": "attribute(number_rows,root,76).\nentity(field,root,0).\nattribute((field,name),0,publication_year).\nattribute((field,type),0,datetime).\nattribute((field,unique),0,35).\nattribute((field,entropy),0,3480).\nentity(field,root,1).\nattribute((field,name),1,conference).\nattribute((field,type),1,string).\nattribute((field,unique),1,4).\nattribute((field,entropy),1,1307).\nattribute((field,freq),1,26).\nattribute((field,min_length),1,3).\nattribute((field,max_length),1,7).\nentity(field,root,2).\nattribute((field,name),2,paper_count).\nattribute((field,type),2,number).\nattribute((field,unique),2,51).\nattribute((field,entropy),2,3820).\nattribute((field,min),2,16).\nattribute((field,max),2,133).\nattribute((field,std),2,24).\nattribute((field,skew),2,1).\nentity(view,root,v0).\nentity(mark,v0,m0).\nrequire(field, publication_year).\nrequire(field, conference).\nrequire(field, paper_count).\nattribute(task,root,summary)." } ], "weights": [ { "feature": "aggregate", "weight": 2 }, { "feature": "bin", "weight": 4 }, { "feature": "bin_high", "weight": 10 }, ... ] } ]The dump itself is represented as a Narwhals dataframe, therefore it can be easily exported / imported e.g. using parquet format.
The
DracoExpressAPI allows the perfect reconstruction of Draco objects at runtime from a dumped state'sprogramandweights, opening opportunities for a mixture-of-experts use case, where applications can route requests to an "ideal" Draco instance. However, for most use cases switching the weights dynamically should also suffice.Weight and rule improvements
Adds a variety of new rules to steer core Draco towards sensible recommendations by default even when starting out from extremely minimal specs, in which we only define which fields we require in the visualization.
Extend computed data schema
skewallows us to reason about usage of log / symlog / linear scale & e.g. whethermeanormedianis better aggregationmin_lengthandmax_lengthallow us to reason about spacing, label angles and chart orientationRenderer refactoring & improvement
rendermethod now optionally accepts a mapping from technical field names to human-readable stringsAlso includes various fixes to remedy some paper-cuts.
WIP. Drafting this PR to facilitate discussions with @domoritz