Skip to content

Commit

Permalink
HREF channel. Ref #1855
Browse files Browse the repository at this point in the history
  • Loading branch information
domoritz committed Jan 12, 2018
1 parent 779d318 commit e927dad
Show file tree
Hide file tree
Showing 18 changed files with 238 additions and 12 deletions.
163 changes: 162 additions & 1 deletion build/vega-lite-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,54 @@
"$ref": "#/definitions/CompositeUnitSpecAlias",
"description": "Unit spec that can have a composite mark."
},
"Conditional<FieldDef>": {
"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`."
},
"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<MarkPropFieldDef>": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -1303,6 +1351,17 @@
],
"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."
},
"href": {
"anyOf": [
{
"$ref": "#/definitions/FieldDefWithCondition"
},
{
"$ref": "#/definitions/ValueDefWithCondition"
}
],
"description": "A URL to load upon mouse click."
},
"opacity": {
"anyOf": [
{
Expand Down Expand Up @@ -1451,6 +1510,17 @@
],
"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."
},
"href": {
"anyOf": [
{
"$ref": "#/definitions/FieldDefWithCondition"
},
{
"$ref": "#/definitions/ValueDefWithCondition"
}
],
"description": "A URL to load upon mouse click."
},
"opacity": {
"anyOf": [
{
Expand Down Expand Up @@ -1715,6 +1785,64 @@
],
"type": "object"
},
"FieldDefWithCondition": {
"additionalProperties": false,
"description": "A FieldDef with Condition<ValueDef>\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<ValueDef>"
},
{
"items": {
"$ref": "#/definitions/Conditional<ValueDef>"
},
"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`."
},
"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<ValueDef>\n{\n condition: {value: ...},\n field: ...,\n ...\n}",
Expand Down Expand Up @@ -4512,7 +4640,8 @@
"color",
"opacity",
"text",
"tooltip"
"tooltip",
"href"
],
"type": "string"
},
Expand Down Expand Up @@ -5831,6 +5960,38 @@
],
"type": "object"
},
"ValueDefWithCondition": {
"additionalProperties": false,
"description": "A ValueDef with Condition<ValueDef | FieldDef>\n{\n condition: {field: ...} | {value: ...},\n value: ...,\n}",
"properties": {
"condition": {
"anyOf": [
{
"$ref": "#/definitions/Conditional<FieldDef>"
},
{
"$ref": "#/definitions/Conditional<ValueDef>"
},
{
"items": {
"$ref": "#/definitions/Conditional<ValueDef>"
},
"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"
},
"MarkPropValueDefWithCondition": {
"additionalProperties": false,
"description": "A ValueDef with Condition<ValueDef | FieldDef>\n{\n condition: {field: ...} | {value: ...},\n value: ...,\n}",
Expand Down
3 changes: 3 additions & 0 deletions scripts/rename-schema.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ perl -pi -e s,'GenericHConcatSpec<CompositeUnitSpec>','HConcatSpec',g build/vega
perl -pi -e s,'GenericUnitSpec<EncodingWithFacet\,AnyMark>','FacetedCompositeUnitSpecAlias',g build/vega-lite-schema.json
perl -pi -e s,'GenericUnitSpec<Encoding\,AnyMark>','CompositeUnitSpecAlias',g build/vega-lite-schema.json

perl -pi -e s,'FieldDefWithCondition<FieldDef\>','FieldDefWithCondition',g build/vega-lite-schema.json
perl -pi -e s,'ValueDefWithCondition<FieldDef\>','ValueDefWithCondition',g build/vega-lite-schema.json

perl -pi -e s,'FieldDefWithCondition<TextFieldDef\>','TextFieldDefWithCondition',g build/vega-lite-schema.json
perl -pi -e s,'ValueDefWithCondition<TextFieldDef\>','TextValueDefWithCondition',g build/vega-lite-schema.json

Expand Down
29 changes: 28 additions & 1 deletion site/docs/encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ The `encoding` property of a single view specification represents the mapping be
"text": ...,
"tooltip": ...,

// Hyperlink Channel
"href": ...,

// Order Channel
"order": ...,

Expand All @@ -51,7 +54,8 @@ The keys in the `encoding` object are encoding channels. Vega-lite supports the

- [Position Channels](#position): `x`, `y`, `x2`, `y2`
- [Mark Property Channels](#mark-prop): `color`, `opacity`, `shape`, `size`
- [Text and Tooltip Channels](#text): `text`, `tooltip`
- [Text and Tooltip Channels](#text): `text`, `tooltip`
- [Hyperlink Channel](#href): `href`
- [Level of Detail Channel](#detail): `detail`
- [Order Channel](#order): `order`
- [Facet Channels](#facet): `row`, `column`
Expand Down Expand Up @@ -204,6 +208,29 @@ In addition to the constant `value`, [value definitions](#value-def) of `text` a

{% include table.html props="condition" source="TextValueDefWithCondition" %}

{:#href}
## Hyperlink Channel

By setting the `href` channel, a mark becomes a hyperlink. The specified URL is loaded upon a muse click. The `cursor` mark property can be set to `pointer` to serve as affordance for hyperlinks.

{% include table.html props="href" source="Encoding" %}


{:#href-field-def}
### Hyperlink Field Definition

In addition to [`field`](field.html), [`type`](type.html), [`bin`](bin.html), [`timeUnit`](timeunit.html) and [`aggregate`](aggregate.html),
[field definitions](#field-def) for the `href` channel can include the `condition` property to specify conditional logic.

{% include table.html props="condition" source="FieldDefWithCondition" %}

{:#href-value-def}
### Hyperlink Value Definition

In addition to the constant `value`, [value definitions](#value-def) of the `href` channel can include the `condition` property to specify conditional logic.

{% include table.html props="condition" source="ValueDefWithCondition" %}


{:#detail}
## Level of Detail Channel
Expand Down
18 changes: 11 additions & 7 deletions src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export namespace Channel {
export const ORDER: 'order' = 'order';
export const DETAIL: 'detail' = 'detail';
export const TOOLTIP: 'tooltip' = 'tooltip';
export const HREF: 'href' = 'href';
}

export type Channel = keyof Encoding<any> | keyof FacetMapping<any>;
Expand All @@ -50,6 +51,7 @@ export const DETAIL = Channel.DETAIL;
export const ORDER = Channel.ORDER;
export const OPACITY = Channel.OPACITY;
export const TOOLTIP = Channel.TOOLTIP;
export const HREF = Channel.HREF;

const UNIT_CHANNEL_INDEX: Flag<keyof Encoding<any>> = {
x: 1,
Expand All @@ -63,7 +65,8 @@ const UNIT_CHANNEL_INDEX: Flag<keyof Encoding<any>> = {
opacity: 1,
text: 1,
detail: 1,
tooltip: 1
tooltip: 1,
href: 1,
};

const FACET_CHANNEL_INDEX: Flag<keyof FacetMapping<any>> = {
Expand Down Expand Up @@ -93,7 +96,7 @@ export const SINGLE_DEF_CHANNELS: SingleDefChannel[] = flagKeys(SINGLE_DEF_CHANN
// Using the following line leads to TypeError: Cannot read property 'elementTypes' of undefined
// when running the schema generator
// export type SingleDefChannel = typeof SINGLE_DEF_CHANNELS[0];
export type SingleDefChannel = 'x' | 'y' | 'x2' | 'y2' | 'row' | 'column' | 'size' | 'shape' | 'color' | 'opacity' | 'text' | 'tooltip';
export type SingleDefChannel = 'x' | 'y' | 'x2' | 'y2' | 'row' | 'column' | 'size' | 'shape' | 'color' | 'opacity' | 'text' | 'tooltip' | 'href';



Expand Down Expand Up @@ -124,9 +127,9 @@ export type PositionScaleChannel = typeof POSITION_SCALE_CHANNELS[0];

// NON_POSITION_SCALE_CHANNEL = SCALE_CHANNELS without X, Y
const {
// x2 and y2 share the same scale as x and y
// text and tooltip has format instead of scale
text: _t, tooltip: _tt,
// x2 and y2 share the same scale as x and y
// text, tooltip, and href have format instead of scale
text: _t, tooltip: _tt, href: _hr,
// detail and order have no scale
detail: _dd, order: _oo,
...NONPOSITION_SCALE_CHANNEL_INDEX
Expand Down Expand Up @@ -159,7 +162,6 @@ export interface SupportedMark {
line?: boolean;
area?: boolean;
text?: boolean;
tooltip?: boolean;
}

/**
Expand All @@ -184,6 +186,7 @@ export function getSupportedMark(channel: Channel): SupportedMark {
case COLOR:
case DETAIL:
case TOOLTIP:
case HREF:
case ORDER: // TODO: revise (order might not support rect, which is not stackable?)
case OPACITY:
case ROW:
Expand Down Expand Up @@ -223,9 +226,10 @@ export function rangeType(channel: Channel): RangeType {
case ROW:
case COLUMN:
case SHAPE:
// TEXT and TOOLTIP have no scale but have discrete output
// TEXT, TOOLTIP, and HREF have no scale but have discrete output
case TEXT:
case TOOLTIP:
case HREF:
return 'discrete';

// Color can be either continuous or discrete, depending on scale type.
Expand Down
1 change: 1 addition & 0 deletions src/compile/mark/area.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const area: MarkCompiler = {

...mixins.color(model),
...mixins.text(model, 'tooltip'),
...mixins.text(model, 'href'),
...mixins.nonPosition('opacity', model),
};
}
Expand Down
1 change: 1 addition & 0 deletions src/compile/mark/bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const bar: MarkCompiler = {
...y(model, stack),
...mixins.color(model),
...mixins.text(model, 'tooltip'),
...mixins.text(model, 'href'),
...mixins.nonPosition('opacity', model)
};
}
Expand Down
1 change: 1 addition & 0 deletions src/compile/mark/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const line: MarkCompiler = {
...mixins.pointPosition('y', model, ref.mid(height)),
...mixins.color(model),
...mixins.text(model, 'tooltip'),
...mixins.text(model, 'href'),
...mixins.nonPosition('opacity', model),
...mixins.nonPosition('size', model, {
vgChannel: 'strokeWidth' // VL's line size is strokeWidth
Expand Down
3 changes: 2 additions & 1 deletion src/compile/mark/mark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,10 @@ export function pathGroupingFields(encoding: Encoding<string>): string[] {
case 'y':
case 'order':
case 'tooltip':
case 'href':
case 'x2':
case 'y2':
// TODO: case 'href', 'cursor':
// TODO: case 'cursor':

// text, shape, shouldn't be a part of line/area
case 'text':
Expand Down
2 changes: 1 addition & 1 deletion src/compile/mark/mixins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function wrapCondition(
}
}

export function text(model: UnitModel, channel: 'text' | 'tooltip' = 'text') {
export function text(model: UnitModel, channel: 'text' | 'tooltip' | 'href' = 'text') {
const channelDef = model.encoding[channel];
return wrapCondition(model, channelDef, channel, (cDef) => ref.text(cDef, model.config));
}
Expand Down
1 change: 1 addition & 0 deletions src/compile/mark/point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function encodeEntry(model: UnitModel, fixedShape?: 'circle' | 'square') {

...mixins.color(model),
...mixins.text(model, 'tooltip'),
...mixins.text(model, 'href'),
...mixins.nonPosition('size', model),
...shapeMixins(model, config, fixedShape),
...mixins.nonPosition('opacity', model),
Expand Down
Loading

0 comments on commit e927dad

Please sign in to comment.