Vega is een visuele programmeer omgeving die je toe laat om heel wat HTML code te reduceren.
Het gebruik van Charts en allerhande diagrammen is één toepassing, maar met je creativiteit kan je nog vele andere toepassingen bedenken.
Binnen de Google Apps Script en Chart omgeving wordt VegaChart als een optie aangeboden om de bestaande charts aan te vullen met modellen die meer "customized" kunnen worden en bruikbaar zijn in een webapp.
Waar Sheets heel wat mogelijkheden heeft, ontbreken er een aantal om te gebruiken in dashboards en webapps, waaronder de radarchart.
Volgende uitleg geeft je de sleutelpunten om een radarchart te koppelen aan data vanuit een sheet.
De integratie in een webapp loopt over een aantal stappen, verder bouwend op het Dashboard principe.
Echter charts die op deze wijze gedefinieerd zijn, kunnen niet aan data gekoppeld worden via de ChartWrapper, waardoor de interactie op een andere wijze dient te gebeuren.
We hergebruiken de functies die reeds voorhanden zijn zoals de doGet() en getSpreadsheetData().
Tevens maken we gebruik van de verschillende lagen, waaronder de opdeling van de presentatie laag in 3 delen:
Index.html
Stylesheet.html
Javascript.html.
De bestaande library die geladen wordt voor visualisatie in Javascript.html wordt vervangen door de recentste versie, waarin de VegaChart geïntegreerd zit.
Bestaand lib:
// Load the Visualization API and the controls package.
google.charts.load('current', {'packages':['corechart', 'controls']});
Nieuwe lib:
// Load the Visualization API and the controls package.
google.charts.load('upcoming', {'packages':['corechart', 'controls','vegachart']});
Binnen deze functie wordt alle data voorbereid voor het maken van het dashboard, dus ook de data die we gaan gebruiken voor de VegaChart - radarchart
We bespreken de hoofdblokken zodat je een idee krijgt van wat er dient te gebeuren.
De details kan je terug vinden in de sourcecode en de documentatie van vega
Vermits de data vanuit de sheet met de functie getSpreadSheetData() als een array wordt doorgegeven, dient deze omgevormd te worden tot een datatable.
Binnen de standaard VegaChart kan slechts één dataTable gemaakt worden en mee gegeven worden als data voor een grafiek.
De functie die hiervoor standaard zou gebruikt worden binnen Google, zijnde
const dataTable = new google.visualization.arrayToDataTable(data,true);
blijkt niet te functioneren.
De methode die gebruikt wordt is om de DataTable aan te maken binnen de bestaande functie en te voorzien van de juiste paramaters, waardoor de VegaChart kan gebruikt worden.
Maak een nieuwe dataTable aan:
const dataTable = new google.visualization.DataTable();
Definieer de kolommen, zoals ze ook gedefinieerd zijn in de sheet.
Hierbij is het belangrijk om ook het formaat mee te geven.
dataTable.addColumn({type: 'string', 'id': 'txcall'});
Als laatste voegen we alle rijen van data toe aan deze datatabel, startende vanaf rij 1, waardoor de header row niet wordt meegenomen als data.
Dit is noodzakelijk omdat VegaChart alle data binnen een kolom verwacht als zijnde van hetzelfde type.
for (r=1; r< data.length-1; r++){dataTable.addRow(data[r])};
const dataTable = new google.visualization.DataTable();
dataTable.addColumn({type: 'string', 'id': 'txcall'});
dataTable.addColumn({type: 'number', 'id': 'median'});
dataTable.addColumn({type: 'number', 'id': 'average'});
dataTable.addColumn({type: 'string', 'id': 'Ant1'});
dataTable.addColumn({type: 'string', 'id': 'Ant2'});
dataTable.addColumn({type: 'number', 'id': 'POWER'});
dataTable.addColumn({type: 'string', 'id': 'LOC'});
dataTable.addColumn({type: 'number', 'id': 'DIST'});
dataTable.addColumn({type: 'number', 'id': 'AZflip'});
dataTable.addColumn({type: 'number', 'id': 'Vmedian'});
dataTable.addColumn({type: 'number', 'id': 'Vaverage'});
for (r=1; r< data.length-1; r++){
dataTable.addRow(data[r])
};
Een VegaChart wordt aangeroepen door gebruik te maken van een object, waarin alle definities zijn opgenomen.
De documentatie kan je vinden op: https://vega.github.io/vega/
Met de Vega Editor kan je een aantal voorbeelden bekijken.
Dit opbject bevat verschillende sub-structuren:
Algemene info Vega, zoals de titel, grootte van de display veld etc
Data structuur, waarin de data wordt ingelezen en getransformeerd naar bruikbare waardes
Signals, welke gebruikt worden om bepaalde variabelen te definieren
Scales, die gebruikt worden om waardes te normaliseren
Verschillende extra mogelijke velden, axis, legends etc
Marks, welke de gegevens gaan bevatten die "getekend" worden.
Deze velden zijn algemeen en bepalen de js lib die dient geraadpleegd te worden:
'vega': {
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "A radar chart, showing AZ plot of WSPR stations.",
"width": 400,
"height": 400,
"padding": 1,
"title": {
"text": "AZplot",
"anchor": "middle",
"fontSize": 14,
"dy": -8,
"dx": {"signal": "-width/4"},
"subtitle": "WSPR RX compare"
},
Vermits deze definitie binnen de functie drawDashboard() gebeurt, wordt de aangemaakte dataTable gebruikt als bron van de data.
Ernaar verwijzen doe je met:
"name": "table","source" : "datatable"
Door gebruik te maken van verschillende transformaties kan je uit de bestaande tabel de data anders ordenen of herrekenen en omvormen naar wat je nodig hebt.
Een voorbeeld is het sorteren van de datatabe volgens een bepaalde kolom:
"name":"AZflip","source": "table",
"transform": [{"type": "collect","sort": {"field": "AZflip"}}]
Een andere mogelijkheid is om zelf een dataset te genereren, zoals we gaan gebruiken voor het maken van de rand van de figuur.
Hiervoor hebben we data nodig voor het tekenen van de lijnen op 30 graden (az30) en iedere 5 graden (az5)
De data voor het tekenen van een lijn wordt bepaald door 2 punten, zijnde [x,y] en [x2,y2].
Beide punten worden hier berekend en de datatabel wordt aangemaakt.
Tevens voorzien we ook data die als display anker kan dienen, zijnde xAz en yAx, zodat op chart bij sommige lijnen een waarde kan geplaatst worden.
"name": "az30",
"transform": [
{"type": "sequence", "start": 0, "stop": 12, "step": 1, "as": "az30deg"},
{"type": "formula","expr": "centerX - cos(PI/2 + (datum.az30deg * PI/6)) * outerRadius","as": "x"},
{"type": "formula","expr": "centerY - sin(PI/2 + (datum.az30deg * PI/6)) * outerRadius","as": "y"},
{"type": "formula","expr": "centerX - cos(PI/2 + (datum.az30deg * PI/6)) * innerRadiusAz6","as": "x2"},
{"type": "formula","expr": "centerY - sin(PI/2 + (datum.az30deg * PI/6)) * innerRadiusAz6","as": "y2"},
{"type": "formula","expr": "centerX - cos(PI/2 + (datum.az30deg * PI/6)) * (innerRadiusAz30 + 50 * max(sizeFactor, 0.4))","as": "xAz"},
{"type": "formula","expr": "centerY - sin(PI/2 + (datum.az30deg * PI/6)) * (innerRadiusAz30 + 50 * max(sizeFactor, 0.4))","as": "yAz"}
]
Signals kan je aanzien als variabelen, die je doorheen de definities kan gebruiken.
Events, zoals mouseclick. mouseover etc kunnen eveneens gekoppeld worden aan signals, waardoor je een interactieve chart kan bekomen.
"signals": [
{"name": "centerX", "init": "width/2"},
{"name": "centerY", "init": "height/2"},
{"name": "radiusRef", "init": "min(width,height)*0.95"},
{"name": "sizeFactor", "init": "radiusRef/400"},
{"name": "outerRadius", "init": "radiusRef/2"},
{"name": "innerRadiusAz6", "init": "radiusRef/2 - (18 * sizeFactor)"},
{"name": "innerRadiusAz30", "init": "radiusRef/2 - (36 * sizeFactor)"},
],
Scales worden gebruikt om data te normaliseren, waardoor bij het tekenen een as kan gebruikt worden met de juiste onderverdeling.
In ons voorbeeld van een radar chart gaan we de scales niet gebruiken voor het maken van een as, daar de data rechtstreeks berekend is in de norm.
Vermits schalen kunnen gebruikt worden voor allerhande voorstellingen (tekst, icoontjes etc) kan je dit ook gebruiken om een kleur toe te kennen aan een object (lijn etc)
het schema category10, geeft je 10 verschillende kleuren voor de schaal.
"scales": [
{
"name": "color",
"type": "ordinal",
"domain": {"data": "table", "field": "category"},
"range": {"scheme": "category10"}
}
],
Alle elementen die getekend worden, worden gedefinieerd met een Mark structuur.
Deze maakt gebruik van de voorgaande definities.
Marks kunnen genest worden, waardoor je één tekenobject kan definiëren met meerdere onderdelen.
Het type geeft weer wat er getekend wordt en afhankelijk van het type zullen er één of twee coordinaten punten nodig zijn.
Een aantal types zijn:
rule: voor een lijn
arc : voor een boog
text: voor een tekst
symbol: voor allerhande symbolen en figuren
...
Het from veld geeft de bron weer van de data die dient gebruikt te worden.
De encode geeft weer hoe de datapunten bepaald worden voor het tekenen.
Met enter wordt een vaste positie gegeven, die zijn data verkrijgt uit de opgegeven waardes
Met update wordt de waarde gehaald uit de vooraf opgebouwde lijst en voor ieder element toegepast.
Verder kunnen er nog extra parameters meegegeven worden voor de visualisatie.
Het eerste gedeelte tekent de buitenste cirkel dmv een arc op een radius en dit over de volledige omtrek (endAngle)
Hierop worden de schaalverdelingen om de 5 graden getekend als zijnde een lijn (rule) tussen 2 punten en met een bepaalde breedte en kleur.
We herhalen hetzelfde voor de lijnen om de 30 graden.
Als laatste plaatsen we op de gepaste plaatsen een tekst, in ons geval is deze bepaald aan de hand van de waarde van de az30deg kolom (tussen 0-12).
We krijgen deze waarde uit de tabel door te verwijzen naar datum en de kolom naam(datum.az30deg) .
Het is eveneens mogelijk om een voorwaarde te koppelen aan de display, zoals hier getoond:
"text": {"signal": "datum.az30deg === 0 ? 360 : (datum.az30deg * 30)"}
Als voorbeeld tekenen we voor ieder datapunt in de tabel een dot, welke gemaakt wordt dmv een kleine gevulde cirkel met een bepaalde kleur.
Merk op dat je hiervoor de update gebruikt, waardoor de tabel wordt afgewerkt tot het laatste element.
{"name": "marks",
"type": "symbol",
"from": {"data": "table"},
"encode": {
"update": {
"x": {"signal": "centerX - cos(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"y": {"signal": "centerY - sin(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"size": {"value":10},
"shape": {"value": "circle"},
"fill": {"value": "#4682b4"}
}
}
}
Voor het tekenen van de lijnen in de grafieken gaan we gebruik maken van een subset of filter stuk van de data.
Je kan dit genereren in de marks setting door het vormen van een nieuwe group (met naam categories), die op zijn beurt gekoppeld wordt aan een bepaald segment van de data, een facet genoemd.
In ons voorbeeld gebruiken we de numerieke kolom "median" om als filter te dienen en zo een nieuwe tabel te maken met naam "facetdata", die we nadien gaan gebruiken voor het tekenen van de lijnen en het opvullen van het vlak.
"type": "group",
"name": "categories",
"zindex": 1,
"from": {
"facet": {"data": "table", "name": "facetdata", "groupby": ["median"]}
},
"marks": [
Hierin kunnen we nu opnieuw tekenelementen definieren.
De lijn die gedefineerd wordt gebruikt de facetdata als uitgangspunt en wordt getekend volgens het line interpolatie type cardinal-closed.
Bij de meeste types kan je achteraan -closed bijvoegen waardoor het laatste lijnstuk terug naar de oorsprong getekend wordt en je een vlak creëert.
Dit vlak wordt dmv de fill opgevuld.
Willen we bij iedere lijn op zijn startpunt nog een ander veld als tekst zichtbaar maken, dan kan je dit door het text element te definiëren en gebruik te maken van de selectie.
Omdat je "dubbel diep" zit in de data, dien je om de waarde uit de tabel te halen 2x datum als referentie te gebruiken:
"text": {"signal": "datum.datum.txcall"},
De volledige option code kan je hieronder terug vinden.
Het tekenen van de Vega chart dient te gebeuren buiten de Dashboard structuur, waardoor de ineractie niet eenvoudig mogelijk is tussen alle elementen van de dashboard structuur.
Echter hiervoor krijg je meer vrijheden voor het definiëren van allerhande grafieken en maps.
Na voorgaande definities kan je de chart gaan tekenen door de code binnen de functie aan te vullen met onderstaande.
De chart wordt getekend dmv de vizualisatie lib VegaChart van google en de koppeling tussen de data en de grafiek gebeurt door de draw method.
Op deze wijze wordt de data die ontvangen is vanuit de sheet gekoppeld aan de grafiek.
Het spreekt voor zich dat je meerdere datatabellen in je script kan maken en deze koppelen aan een andere instantie van de grafiek, waardoor je meerdere radar charts op één dashboard kan tekenen.
Ook kan je meerdere curves binnen één chart tekenen, door gebruik te maken van de mark group filters met telkens een nieuw set van punten.
Om meer varianten te bekijken, kan je gebruik maken van de examples, deze zullen je zeker op weg helpen.
// Create radar chart
const elem = document.getElementById('radar-div');
elem.setAttribute("style", "display: inline-block; width: 400px; height: 400px; padding: 20px;");
var radarChart = new google.visualization.VegaChart(elem);
radarChart.draw(dataTable, options);
<html>
<head>
<!--Load the AJAX API-->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
// Load the Visualization API and the controls package.
google.charts.load('upcoming', {'packages':['corechart', 'controls','vegachart']});
// Set a callback to run when the Google Visualization API is loaded.
// google.charts.setOnLoadCallback(drawDashboard);
google.charts.setOnLoadCallback(sendQuery);
// Callback that creates and populates a data table,
// instantiates a dashboard, a range slider and a pie chart,
// passes in the data and draws it.
/**
* Issue asynchronous request for spreadsheet data.
* From gist.github.com/mogsdad/60dcc4116ed74fceb5f9
*/
function sendQuery() {
google.script.run
.withSuccessHandler(drawDashboard)
.withFailureHandler(function(msg) {
// Respond to failure conditions here.
$('#main-heading').text(msg);
$('#main-heading').addClass("error");
$('#error-message').show();
})
.getSpreadsheetData();
}
function drawDashboard(data) {
// const dataTable=data
// const dataTable = new google.visualization.arrayToDataTable(data,true);
const dataTable = new google.visualization.DataTable();
dataTable.addColumn({type: 'string', 'id': 'txcall'});
dataTable.addColumn({type: 'number', 'id': 'median'});
dataTable.addColumn({type: 'number', 'id': 'average'});
dataTable.addColumn({type: 'string', 'id': 'Ant1'});
dataTable.addColumn({type: 'string', 'id': 'Ant2'});
dataTable.addColumn({type: 'number', 'id': 'POWER'});
dataTable.addColumn({type: 'string', 'id': 'LOC'});
dataTable.addColumn({type: 'number', 'id': 'DIST'});
dataTable.addColumn({type: 'number', 'id': 'AZflip'});
dataTable.addColumn({type: 'number', 'id': 'Vmedian'});
dataTable.addColumn({type: 'number', 'id': 'Vaverage'});
for (r=1; r< data.length-1; r++){
dataTable.addRow(data[r])
};
const options = {
'vega': {
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "A radar chart, showing AZ plot of WSPR stations.",
"width": 400,
"height": 400,
"padding": 1,
"title": {
"text": "AZplot",
"anchor": "middle",
"fontSize": 14,
"dy": -8,
"dx": {"signal": "-width/4"},
"subtitle": "WSPR RX compare"
},
"data": [
{
"name": "table",
"source" : "datatable"
},
{
"name": "txcall",
"source": "table",
"transform": [
{
"type": "aggregate",
"groupby": ["txcall"]
}
]
},
{
"name":"AZflip",
"source": "table",
"transform": [
{
"type": "collect",
"sort": {"field": "AZflip"}
}
]
},
{
"name": "keys",
"source": "table",
"transform": [
{
"type": "aggregate",
"groupby": ["key"]
}
]
},
{
"name": "az30",
"transform": [
{"type": "sequence", "start": 0, "stop": 12, "step": 1, "as": "az30deg"},
{
"type": "formula",
"expr": "centerX - cos(PI/2 + (datum.az30deg * PI/6)) * outerRadius",
"as": "x"
},
{
"type": "formula",
"expr": "centerY - sin(PI/2 + (datum.az30deg * PI/6)) * outerRadius",
"as": "y"
},
{
"type": "formula",
"expr": "centerX - cos(PI/2 + (datum.az30deg * PI/6)) * innerRadiusAz6",
"as": "x2"
},
{
"type": "formula",
"expr": "centerY - sin(PI/2 + (datum.az30deg * PI/6)) * innerRadiusAz6",
"as": "y2"
},
{
"type": "formula",
"expr": "centerX - cos(PI/2 + (datum.az30deg * PI/6)) * (innerRadiusAz30 + 50 * max(sizeFactor, 0.4))",
"as": "xAz"
},
{
"type": "formula",
"expr": "centerY - sin(PI/2 + (datum.az30deg * PI/6)) * (innerRadiusAz30 + 50 * max(sizeFactor, 0.4))",
"as": "yAz"
}
]
},
{
"name": "az5",
"transform": [
{"type": "sequence", "start": 0, "stop": 72, "step": 1, "as": "az5deg"},
{
"type": "formula",
"expr": "centerX - cos(PI/2 + (datum.az5deg * PI/36)) * outerRadius",
"as": "x"
},
{
"type": "formula",
"expr": "centerY - sin(PI/2 + (datum.az5deg * PI/36)) * outerRadius",
"as": "y"
},
{
"type": "formula",
"expr": "centerX - cos(PI/2 + (datum.az5deg * PI/36)) * innerRadiusAz6",
"as": "x2"
},
{
"type": "formula",
"expr": "centerY - sin(PI/2 + (datum.az5deg * PI/36)) * innerRadiusAz6",
"as": "y2"
}
]
}
],
"signals": [
{"name": "centerX", "init": "width/2"},
{"name": "centerY", "init": "height/2"},
{"name": "radiusRef", "init": "min(width,height)*0.95"},
{"name": "sizeFactor", "init": "radiusRef/400"},
{"name": "outerRadius", "init": "radiusRef/2"},
{"name": "innerRadiusAz6", "init": "radiusRef/2 - (18 * sizeFactor)"},
{"name": "innerRadiusAz30", "init": "radiusRef/2 - (36 * sizeFactor)"},
],
"scales": [
{
"name": "color",
"type": "ordinal",
"domain": {"data": "table", "field": "category"},
"range": {"scheme": "category10"}
}
],
"marks": [
{
"type": "arc",
"encode": {
"enter": {
"x": {"signal": "centerX"},
"y": {"signal": "centerY"},
"startAngle": {"value": 0},
"endAngle": {"signal": "2*PI"},
"outerRadius": {"signal": "outerRadius"},
"fill": {"value": "white"},
"stroke": {"value": "gray"}
}
}
},
{
"type": "rule",
"from": {"data": "az5"},
"encode": {
"enter": {
"x": {"field": "x"},
"y": {"field": "y"},
"x2": {"field": "x2"},
"y2": {"field": "y2"},
"strokeWidth": {"signal": "pow(2*sizeFactor, 0.2)"},
"stroke": {"value": "red"}
}
}
},
{
"type": "rule",
"from": {"data": "az30"},
"encode": {
"enter": {
"size": {"signal": "pow(2*sizeFactor, 4)"},
"x": {"field": "x"},
"y": {"field": "y"},
"x2": {"field": "x2"},
"y2": {"field": "y2"},
"strokeWidth": {"signal": "pow(2*sizeFactor, 2)"},
"stroke": {"value": "blue"}
}
}
},
{
"type": "text",
"from": {"data": "az30"},
"encode": {
"enter": {
"x": {"field": "xAz"},
"y": {"field": "yAz"},
"align": {"value": "center"},
"fill": {"value": "black"},
"baseline": {"value": "middle"},
"text": {"signal": "datum.az30deg === 0 ? 360 : (datum.az30deg * 30)"},
"fontSize": {"signal": "14*max(sizeFactor, 0.4)"},
"fontWeight": {"value": "100"}
}
}
},
{
"name": "marks",
"type": "symbol",
"from": {"data": "table"},
"encode": {
"update": {
"x": {"signal": "centerX - cos(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"y": {"signal": "centerY - sin(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"size": {"value":10},
"shape": {"value": "circle"},
"fill": {"value": "#4682b4"}
}
}
},
{
"type": "group",
"name": "categories",
"zindex": 1,
"from": {
"facet": {"data": "table", "name": "facetdata", "groupby": ["median"]}
},
"marks": [
{
"type": "line",
"name": "category-line",
"from": {"data": "facetdata"},
"encode": {
"enter": {
"interpolate": {"value": "cardinal-closed"},
"x": {"signal": "centerX - cos(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"y": {"signal": "centerY - sin(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"stroke": {"scale": "color", "field": "category"},
"strokeWidth": {"value":1},
"fill": {"scale": "color", "field": "category"},
"fillOpacity": {"value": 0.1}
}
}
},
{
"type": "text",
"name": "value-text",
"from": {"data": "category-line"},
"encode": {
"enter": {
"x": {"signal": "datum.x"},
"y": {"signal": "datum.y"},
"text": {"signal": "datum.datum.txcall"},
"align": {"value": "center"},
"baseline": {"value": "middle"},
"fill": {"value": "black"}
}
}
}
]
}
]
}
};
// Create a dashboard.
var dashboard = new google.visualization.Dashboard(
document.getElementById('dashboard_div'));
// Create a range slider, passing some options
var donutRangeSlider = new google.visualization.ControlWrapper({
'controlType': 'NumberRangeFilter',
'containerId': 'slider-div',
'options': {
'filterColumnLabel': 'AZflip'
}
});
// Create filter selector
var genderPicker = new google.visualization.ControlWrapper({
'controlType': 'CategoryFilter',
'containerId': 'selector-div',
'options': {
'filterColumnLabel': 'LOC'
}
});
// Create a pie chart, passing some options
var pieChart = new google.visualization.ChartWrapper({
'chartType': 'PieChart',
'containerId': 'piechart-div',
'view': {columns : [0,5]},
// 'options': {
// 'width': 300,
// 'height': 300,
// 'pieSliceText': 'value',
// 'legend': 'right'
// }
});
// Create
var table = new google.visualization.ChartWrapper({
'chartType': 'Table',
'containerId': 'table-div',
});
// Establish dependencies, declaring that 'filter' drives 'pieChart',
// so that the pie chart will only display entries that are let through
// given the chosen slider range.
dashboard.bind(donutRangeSlider, [pieChart,table]);
dashboard.bind(genderPicker,[pieChart,table]);
// Draw the dashboard.
dashboard.draw(data);
// Create radar site
const elem = document.getElementById('radar-div');
elem.setAttribute("style", "display: inline-block; width: 400px; height: 400px; padding: 20px;");
var radarChart = new google.visualization.VegaChart(elem);
radarChart.draw(dataTable, options);
}
</script>
</head>
</html>
Omdat Vega heel wat extra's toe laat sommen we hier een aantal speciale gevallen op, waarvoor de documentatie ontoereikend is.
Wanneer je wil dat er bepaalde zaken gebeuren, zoals de kleur veranderen etc, als je over een element komt, dan kan je hiervoor de hover optie gebruiken.
Met de enter definieer je de toestand bij het eerst aanmaken van het element, met de hover defineer je de toestand wanneer je met je muis over het element beweegt en met de update definieer je de toestand wanneer je het element terug verlaat.
In het volgende voorbeeld veranderen we de fill en stroke eigenschappen van een lijn
"type": "line",
"name": "category-line",
"from": {"data": "facetdata"},
"encode": {
"enter": {
"interpolate": {"value": "cardinal-closed"},
"x": {"signal": "centerX - cos(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"y": {"signal": "centerY - sin(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"stroke": {"scale": "color", "field": "category"},
"strokeWidth": {"value":0.5},
"fill": {"scale": "color", "field": "category"},
"fillOpacity": {"value": 0.2},
},
"update": {
"stroke": {"scale": "color", "field": "category"},
"strokeOpacity": {"value": 0.2},
"fill": {"scale": "color", "field": "category"},
"fillOpacity": {"value": 0.2}
},
"hover": {
"stroke": {"value": "#f00"},
"strokeOpacity": {"value": 1},
"fill": {"value":"red"},
"fillOpacity": {"value":1}
}
}
Het is eveneens mogelijk om met Events te werken om een bepaalde actie uit te voeren.
In het volgende voorbeeld gebruiken we twee events wanneer we over de mark stations bewegen, zijnde mouseover en mouseout, die telkens een waarde geven aan het hoverSignal via een update actie.
De default value is false en de verandering gebeurt bij een event.
Omdat het om twee events gaat, dien je beide te definieren binnen een array.
{"name": "hoverSignal",
"value": "false",
"on": [
{"events": "@stations:mouseover", "update": "true"},
{"events": "@stations:mouseout", "update": "false"}
]
},
Deze waarde kunnen we nadien afvragen in een actie, zoals de fill actie in de update (niet de enter) actie van een element.
"fill": [{"test": "hoverSignal", "value":"red"},{"value": "#4682b4"}],
De test controleert of het hoverSignal true of false is, waarna bij true, de waarde red wordt toegekend aan de fill.
Indien false, wordt de default value #4682b4 toegekend, welke ook zou worden toegekend als de voorwaarde niet bestond.
Omdat je gebruik maakt van een test en default waarde, worden beide als object in een array gezet voor de fill actie.
"signals": [
...
{"name": "hoverSignal", "value": "false","on": [
{"events": "@stations:mouseover", "update": "true"},
{"events": "@stations:mouseout", "update": "false"}
]
},
"mark": {
"name": "stations",
"type": "symbol",
"tooltip": {"content": "data"},
"zindex": 3,
"from": {"data": "table"},
"encode": {
"enter":{
"x": {"signal": "centerX - cos(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"y": {"signal": "centerY - sin(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage", "offset": 5},
"size": {"value":100},
"shape": {"value": "circle"},
"fill":{"value": "#4682b4"},
"cursor": {"value": "pointer"},
"tooltip": {"signal": "{'TXcallsign':datum.txcall,'Locator':datum.LOC,'Dist': datum.DIST + 'km','AZ':datum.AZflip,'Avg':datum.average+'dBm','Median':datum.median+'dBm','Ant':datum.Ant1 + ' vs ' + datum.Ant2}"}
},
"update":{
"x": {"signal": "centerX - cos(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"y": {"signal": "centerY - sin(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage", "offset": 5},
"size": {"value":100},
"shape": {"value": "circle"},
"fill": [{"test": "hoverSignal", "value":"red"},{"value": "#4682b4"}],
},
}
},
Het gebruik van een tooltip laat je toe om bepaalde waardes te tonen indien je met je muis over een punt in een grafiek komt.
Meestal beperkt zich dit tot één waarde, maar het is ook mogelijk om verschillende waardes te laten zien.
Zo kan je een titel en een waarde laten zien, die telkens op een volgende lijn wordt getoond (voorbeeld met 2 titels en waardes).
Door de cursor als pointer te definiëren wordt de standaard pijl aangepast naar een handje als je over een datapunt komt waaraan een tooltip zit.
In het volgende voorbeeld laten we twee waardes zien wanneer we met de muis over een datapunt bewegen.
"name": "stations",
"type": "symbol",
"tooltip": {"content": "data"},
"zindex": 2,
"from": {"data": "table"},
"encode": {
"enter":{
"x": {"signal": "centerX - cos(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage"},
"y": {"signal": "centerY - sin(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * datum.Vaverage", "offset": 5},
"size": {"value":100},
"shape": {"value": "circle"},
"fill":{"value": "#4682b4"},
"cursor": {"value": "pointer"},
"tooltip": {"signal": "{'TXcallsign':datum.txcall,'Locator':datum.LOC}"}
},
het gebruik van scales laat je toe om op een eenvoudige manier de data te plaatsen in een grafiek.
In het volgende voorbeeld maken we voor de radar chart een scale aan radial die de waardes uit één kolom normaliseert tussen 0 en 1.
Het betreft een lineaire schaal met als range tussen 0 en 1.
Het veld dat gebruikt wordt om de min en max waarde te bepalen is DIST uit de tabel en vermits we de waardes vanaf het nulpunt willen laten starten, geven we het domainMin in als 0.
{
"name": "radial",
"type": "linear",
"range": {"signal": "[0, 1]"},
"zero": true,
"nice": false,
"domain": {"data": "table", "field": "DIST"},
"domainMin": 0
},
Hierna kan aan de hand van deze waarde een punt positie berekend worden voor het teken van de grafiek.
"x": {"signal": "centerX - cos(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * (scale('radial', datum.DIST))"},
"y": {"signal": "centerY - sin(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * (scale('radial', datum.DIST))"},
"scales": [
{
"name": "color",
"type": "ordinal",
"domain": {"data": "table", "field": "category"},
"range": {"scheme": "category10"}
},
{
"name": "radial",
"type": "linear",
"range": {"signal": "[0, 1]"},
"zero": true,
"nice": false,
"domain": {"data": "table", "field": "DIST"},
"domainMin": 0
},
],
......
"mark":{
"name": "stations-dist",
"type": "symbol",
"tooltip": {"content": "data"},
"zindex": 3,
"from": {"data": "table"},
"encode": {
"enter":{
"x": {"signal": "centerX - cos(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * (scale('radial', datum.DIST))"},
"y": {"signal": "centerY - sin(PI/2 + (datum.AZflip * PI/180)) * innerRadiusAz6 * (scale('radial', datum.DIST))"},
"size": {"value":10},
"shape": {"value": "circle"},
"fill":{"value": "#f642b4"},
"cursor": {"value": "pointer"},
"tooltip": {"signal": "{'TXcallsign':datum.txcall,'Locator':datum.LOC,'Dist': datum.DIST + 'km','AZ':datum.AZflip,'Avg':datum.average+'dBm','Median':datum.median+'dBm','Ant':datum.Ant1 + ' vs ' + datum.Ant2}"}
},
}
},
Vermits Vega een grafische taal is die gebruik maakt ven de HTML DOM omgeving, worden alle font en anders formaten niet altijd geconfigureerd binnen vega zelf.
Zo is het definiëren van de Signal text grootte mogelijk via de CSS class setting:
vega-bind voor ieder element en vega-bindings, voor de ganse container van signals
vega-tooltip-style.
Stylesheet.html
.vega-bind {
font-size: 14px;
text-align: centerX;
}
Javascript.html
"signals": [
{
"name": "labels", "value": true,
"bind": {"input": "checkbox"}
},
{
"name": "radius", "value": 280,
"bind": {"input": "range", "min": 20, "max": 600}
},
{
"name": "extent", "value": 360,
"bind": {"input": "range", "min": 0, "max": 360, "step": 1}
},
{
"name": "rotate", "value": 0,
"bind": {"input": "range", "min": 0, "max": 360, "step": 1}
},
{
"name": "layout", "value": "tidy",
"bind": {"input": "radio", "options": ["tidy", "cluster"]}
},
{
"name": "links", "value": "line",
"bind": {
"input": "select",
"options": ["line", "curve", "diagonal", "orthogonal"]
}
},
{ "name": "originX", "update": "width / 2" },
{ "name": "originY", "update": "height / 2" }
],