I am generating some dynamic vizframe column charts by looping at the response I get from my Odata service. One of my requirement for the chart is to show columns in different color depending on the value of a field I have in data. Let's call it validation status.
I have a fairly good idea on how to achieve this in normal situations by using the setVizProperties method and setting up rules for the dataPointStyle properties. It would still have been possible if I had the same criteria for all the charts and all the values. But that isn't the case since I need to check each record individually to determine it's status. So I thought of using the callback functionality of the dataPointStyle. But the problem here is that although it gives me the context but it doesn't tell me from which chart this callback has been triggered. My idea is that if I get the chart name or it's reference then I can access it's model and determine the color.
So if I can somehow get a reference of the vizframe from where the call back is being triggered, it will solve my problem.
callback
Description:
function (data, extData) {...} => true|false
A function to determine whether a given data matches the rule. Parameters:
data is an object with all bound field ids as keys, and corresponding values as values. It helps to consider it as the object containing everything you see in datapoint mouseover tooltip. If unbound dimensions or measures are set in FlatTableDataset context field, the related key/value pairs will be included in this parameter as well.
extData is an object with all other measure fields that in the same line with current data point. Measure Ids as keys, and corresponding values as values. It helps to compare value between different measures.
Link to the vizFrame documentation
JSFiddle
My Data looks something like this:
[{
"RunLogId": "0000000040",
"RuleId": "00016",
"CreatedOn": "2020-07-21",
"CreatedAt": "09:44:35",
"NAV_SUBSCRIBED_LOGS": {
"results": [
{
"RunLogId": "0000000040",
"Sequence": "00001",
"RuleId": "00016",
"Variation": "-3.94",
"ValidationStatus": "F",
"Dimension": "ABC"
},
{
"RunLogId": "0000000040",
"Sequence": "00002",
"RuleId": "00016",
"Variation": "1.04",
"ValidationStatus": "S",
"Dimension": "DEF"
}
]
}
},
{
"RunLogId": "0000000033",
"RuleId": "00014",
"CreatedOn": "2020-07-15",
"CreatedAt": "11:10:09",
"NAV_SUBSCRIBED_LOGS": {
"results": [
{
"RunLogId": "0000000033",
"Sequence": "00001",
"RuleId": "00014",
"Variation": "-2.36",
"ValidationStatus": "F",
"Dimension": "ABC"
},
{
"RunLogId": "0000000033",
"Sequence": "00002",
"RuleId": "00014",
"Variation": "-5.05",
"ValidationStatus": "F",
"Dimension": "DEF"
}
]
}
}]
My code looks some like this :
for (var i = 0; i < chartsCount; i++) {
var oModel = new JSONModel();
var chartData = aSubscriptions[i].NAV_SUBSCRIBED_LOGS.results;
var aDimensions = [];
var aDimFeeds = [];
aDimensions.push({
name: "Dimension",
value: "{Dimension}"
});
aDimFeeds.push("Dimension");
oModel.setData(chartData);
oModel.refresh();
var oDataset = new FlattenedDataset({
dimensions: aDimensions,
measures: [{
name: "Variation",
value: "{Variation}"
}],
data: {
path: "/"
}
});
var oVizFrame = new VizFrame();
oVizFrame.setVizType("column");
oVizFrame.setHeight("450px");
oVizFrame.setDataset(oDataset);
oVizFrame.setModel(oModel);
var feedValueAxisActual = new sap.viz.ui5.controls.common.feeds.FeedItem({
"uid": "valueAxis",
"type": "Measure",
"values": ["Variation"]
}),
feedCategoryAxis = new sap.viz.ui5.controls.common.feeds.FeedItem({
"uid": "categoryAxis",
"type": "Dimension",
"values": aDimFeeds
});
oVizFrame.addFeed(feedValueAxisActual);
oVizFrame.addFeed(feedCategoryAxis);
oVizFrame.setVizProperties({
plotArea: {
dataPointStyle: {
"rules": [
{
callback: function (oContext, extData) {
that.checkValue(oContext, "S");
},
"properties": {
"color": "sapUiChartPaletteSemanticGoodLight1"
},
"displayName": "Successful"
}
, {
callback: function (oContext, extData) {
that.checkValue(oContext, "F");
},
properties: {
color: "sapUiChartPaletteSemanticBadLight1"
},
"displayName": "Failed"
}
],
others: {
properties: {
color: "sapUiChartPaletteSemanticNeutral"
},
"displayName": "Undefined"
}
}
}
});
//Chart Container
var oChartContainer = new ChartContainer();
var oChartContainerContent = new ChartContainerContent();
oChartContainerContent.setContent(oVizFrame);
oChartContainer.addContent(oChartContainerContent);
}
Not sure if I understand you correctly but I'll give it a shot anyway. If I was wrong let me know.
You create charts in a loop. You want to access the specific chart in a callback.
Why don't you access oVizFrame in your callback?
First I would replace the for loop with a forEach. forEach calls a given function for every element in your array:
aSubscriptions.forEach(function(oSubscription) {
const oModel = new JSONModel();
const chartData = oSubscription.NAV_SUBSCRIBED_LOGS.results;
...
}
In a for loop your variables are reused. In a forEach function a new scope is created for every item. So when you access oVizFrame in your callback it is the same oVizFrame that you declared earlier.
Then you should be able to access oVizFrame in your callback.
oVizFrame.setVizProperties({
plotArea: {
dataPointStyle: {
"rules": [{
callback: function(oContext, extData) {
// >>>>>>>> Do something with oVizFrame <<<<<<<<
that.checkValue(oContext, "S");
},
...
}, {
callback: function(oContext, extData) {
// >>>>>>>> Do something with oVizFrame <<<<<<<<
that.checkValue(oContext, "F");
},
...
}],
...
}
}
});
Related
I'm using the Google Chart Tools Directive Module to draw a line/area chart in my Angularjs application, from rdf data retrieved via sparql queries and available, within the app, in json-like format.
In the main controller I declared my drawing function like this:
$scope.createChart = function () {
var json1 = $scope.entities // here I have my data
var rows = []
// populate array with data:
for (var key in json1) {
if (json1[key]['qb:dataset'] == $scope.dsUri) {
var date = new Date(json1[key]['sdmx-dimension:refTime']);
var deads = json1[key]['dpc:deads'];
var newpos = json1[key]['dpc:newPositive'];
var intcare = json1[key]['dpc:intensiveCare'];
rows.push({ c: [ { v:date }, { v:deads }, { v:newpos }, { v:intcare} ] });
}
}
// sort rows by dates
rows.sort(function (rowA, rowB) {
return rowA.c[0].v.getTime() - rowB.c[0].v.getTime();
});
// define chart object
$scope.myChartObject = {
"type": "LineChart",
"data": {
"cols": [
{
"id": "date",
"label": "Date",
"type": "date"
},
{
"id": "deaths",
"label": "Deaths",
"type": "number"
},
{
"id": "newpos",
"label": "New Positive",
"type": "number"
},
{
"id": "intCare",
"label": "Intensive Care",
"type": "number"
}
]
},
"options": {
"title": "Observations",
"height": 400,
"legend": { position: 'bottom' },
"width": 'auto'
}
}
// add rows to data
$scope.myChartObject.data.rows = rows;
return $scope.myChartObject;
}
}]);
And in my HTML I got my chart div:
<div google-chart chart="createChart()" class="mychartClass"></div>
Now the problem with this solution is that the chart gets blank drawn first and - if query doesn't take much time - filled later.
How to wait for data to be retrieved from queries before drawing the chart?
I've tried setting a timeout but this is not the best way to go.
It is better to have the chart directive follow a scope variable:
̶<̶d̶i̶v̶ ̶g̶o̶o̶g̶l̶e̶-̶c̶h̶a̶r̶t̶ ̶c̶h̶a̶r̶t̶=̶"̶c̶r̶e̶a̶t̶e̶C̶h̶a̶r̶t̶(̶)̶"̶ ̶c̶l̶a̶s̶s̶=̶"̶m̶y̶c̶h̶a̶r̶t̶C̶l̶a̶s̶s̶"̶>̶<̶/̶d̶i̶v̶>̶
<div google-chart chart="myChart" class="mychartClass"></div>
Using a function causes the AngularJS framework to invoke the function every digest cycle. This leads to doing alot of unnecessary computation.
Instead do the computation after the data arrives from the server.
$http.get(url).then(function(response) {
$scope.entities = response.data;
$scope.myChart = $scope.createChart();
});
This way the chart is only computed once when the data arrives from the server.
I'm creating a JSON object from an array and I want to dynamically push data to this JSON object based on the values from array. See my code for a better understanding of my problem...
for(i=0;i<duplicates.length; i++) {
var request = {
"name": duplicates[i].scope,
"id": 3,
"rules":[
{
"name": duplicates[i].scope + " " + "OP SDR Sync",
"tags": [
{
"tagId": 1,
"variables":[
{
"variable": duplicates[i].variable[j],
"matchType": "Regex",
"value": duplicates[i].scopeDef
}
],
"condition": false,
},
{
"tagId": 1,
"condition": false,
}
],
"ruleSetId": 3,
}
]
}
}
I take object properties from the duplicates array that can have the following elements:
[{scopeDef=.*, scope=Global, variable=[trackingcode, v1, v2]}, {scopeDef=^https?://([^/:\?]*\.)?delta.com/products, scope=Products Section, variable=[v3]}]
As you can see, an object contain variable element that can have multiple values. I need to push to the JSON object all those values dynamically (meaning that there could be more than 3 values in an array).
For example, after I push all the values from the duplicates array, my JSON object should look like this:
name=Products Section,
rules=
[
{
name=Products Section OP SDR Sync,
tags=[
{
variables=
[
{
matchType=Regex,
variable=v3,
value=^https?://([^/:\?]*\.)?delta.com/products
},
{
matchType=Regex,
variable=trackingcode,
value=.*
},
{
matchType=Regex,
variable=v1,
value=.*
},
{
matchType=Regex,
variable=v2,
value=.*
}
],
condition=false,
},
{
condition=false,
tagId=1
}
],
ruleSetId=3
}
]
}
I tried the following code but without success:
for(var j in duplicates[i].variable) {
var append = JSON.parse(request);
append['variables'].push({
"variable":duplicates[i].variable[j],
"matchType": "Regex",
"value": duplicates[i].scopeDef
})
}
Please let me know if I need to provide additional information, I just started working with JSON objects.
First of all, you dont need to parse request, you already create an object, parse only when you get JSON as string, like:
var json='{"a":"1", "b":"2"}';
var x = JSON.parse(json);
Next, you have any property of object wrapped in arrays. To correctly work with it you should write:
request.rules[0].tags[0].variables.push({
"variable":duplicates[i].variable[j],
"matchType": "Regex",
"value": duplicates[i].scopeDef
})
If you want to use your code snippet, you need some changes in request:
var request = {
"name": duplicates[i].scope,
"id": 3,
"variables":[
{
"variable": duplicates[i].variable[j],
"matchType": "Regex",
"value": duplicates[i].scopeDef
}
],
"rules":[
{
"name": duplicates[i].scope + " " + "OP SDR Sync",
"tags": [
{
"tagId": 1,
"condition": false,
},
{
"tagId": 1,
"condition": false,
}
],
"ruleSetId": 3,
}
]
}
}
To understand JSON remember basic rule: read JSON backward. It means:
property
object.property
arrayOfObfects['id'].object.property
mainObject.arrayOfObfects['id'].object.property
and so on. Good luck!
I need to remove an object from an JSON tree. I know a reference to that object. Is there a nice way to do it via JavaScript or jQuery besides traversing the whole tree?
Example:
party = {
"uuid": "4D326531-3C67-4CD2-95F4-D1708CE6C7A8",
"link": {
"rel": "self",
"href": "http://localhost:8080/cim/party/4D326531-3C67-4CD2-95F4-D1708CE6C7A8"
},
"type": "PERSON",
"name": "John Doe",
"properties": {
"CONTACT": [
{
"category": "CONTACT",
"type": "EMAIL",
"key": "email",
"value": "john.doe#doe.at",
"id": "27DDFF6E-5235-46BF-A349-67BEC92D6DAD"
},
{
"category": "CONTACT",
"type": "PHONE",
"key": "mobile",
"value": "+43 999 999990 3999",
"id": "6FDAA4C6-9340-4F11-9118-F0BC514B0D77"
}
],
"CLIENT_DATA": [
{
"category": "CLIENT_DATA",
"type": "TYPE",
"key": "client_type",
"value": "private",
"id": "65697515-43A0-4D80-AE90-F13F347A6E68"
}
]
},
"links": []
}
And i have a reference: contact = party.properties.contact[1]. And I want to do something like delete contact.
You may delete it this way. I just tested it.
var party = {
// ...
}
alert(party.properties.CONTACT[0]) // object Object
delete party.properties.CONTACT[0] // true
alert(party.properties.CONTACT[0]) // undefined
Fiddle
UPDATE
In the case above party is a direct property of window object
window.hasOwnProperty('party'); // true
and that's why you can't delete a property by reference. Anyhow, behavior of delete operator with host objects is unpredictable. Though, you may create a scope around the party object and then you'll be allowed to delete it.
var _scope = {};
var _scope.party = {
// ...
};
var r = _scope.party.properties.CONTACT[0];
window.hasOwnProperty('party'); // false
alert(r) // object Object
delete r // true
alert(r) // undefined
It only works one way: a variable holds a reference, but there is no way given a particular reference to infer what variables hold it (without iterating over them and comparing).
I'm having a radial graph showing two levels of nodes. On clicking a node it is possible to add a sub graph with calling the sum() function. Everything works fine except setting individual color for the newly added edges.
Does anybody have ever tried to load sub graphs with individual edge colors or have a hint what I'm doing wrong?
Here I'm getting and adding the sub graph:
subtree = getSubtree(node.id);
//perform animation.
subtree.success(function(data){
rg.op.sum(data, {
type: 'fade:seq',
fps: 40,
duration: 1000,
hideLabels: false,
});
});
I've checked also the loaded data but for me it seems to be totally equal. I've also loaded the same data into the initial graph instead of the sub graph and then it was colored correct. Nevertheless here is some test data which is the result of the function getSubtree (the id "placeholder" matches the id of the existing where the sub graph should be added):
{
"id": "placeholder1",
"name": "country",
"children": [{
"id": "2_3mSV~_scat_1",
"name": "hyponym",
"children": [{
"children": [],
"adjacencies": {
"nodeTo": "2_3mSV~_scat_1",
"data": {
"$color": "#29A22D"
}
},
"data": {
"$color": "#29A22D"
},
"id": "3_58z3q_sc_174_6",
"name": "location"
}],
"data": {
"$type": "star",
"$color": "#666666"
},
"adjacencies": [{
"nodeTo": "3_58z3q_sc_174_6",
"data": {
"$color": "#29A22D"
}
}]
}]
}
I finally found the problem in the framework itself...
When calling the construct function inside the call of sum() which is actually adding the subtree then the data object containing information about the adjacence's individual visualization is not used for adding the new adjacence. Therefore I changed the code manually (this for loop is the new version of the existing for loop inside the construct() function):
for(var i=0, ch = json.children; i<ch.length; i++) {
//CUSTOM CODE: GET DATA OF THIS ADJACENCE
data = null;
if(ch[i].adjacencies[0]==undefined){
data = ch[i].adjacencies.data;
}
else{
data = ch[i].adjacencies.data;
}
ans.addAdjacence(json, ch[i], data);
arguments.callee(ans, ch[i]);
//CUSTOM CODE END
}
I'm working with a response from the Webtrends API in Google apps script and I have a JSON/JS object that looks like this:
"data": [
{
"period": "Month",
"start_date": "2013-12",
"end_date": "2013-12",
"attributes": {},
"measures": {
"Visits": 500
},
"SubRows": [
{
"facebook.com": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"google.co.uk": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"newsnow.co.uk": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"No Referrer": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"t.co": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
}
}
]
}
]
What I need to access is the names i.e facebook.com etc... and visit numbers for each of the SubRows.
I'm able to get the visit numbers, but I can't work out how to get the names. Please note the names will change constantly as different sites will send different amounts of traffic each day.
Section of my code at the moment where I get the visit numbers:
for(i in dObj){
var data = dObj[i].SubRows;
var sd = dObj[i].start_date;
var ed = dObj[i].end_date;
if(sd == ed){
var timep = ""+ sd;
}
else{
var timep = ""+ sd + "-" + ed;
}
var subRows = data[0];
Logger.log(subRows);
for(i in subRows){
var row = subRows[i];
var rmeasures = row.measures;
var rvis = rmeasures.Visits;
values = [timep,"",rvis]; //Blank string for where the name of the site would go
}
}
I've tried the following links, but none of them seem to have the answer:
Getting JavaScript object key list
How to access object using dynamic key?
How to access key itself using javascript
How do I access properties of a javascript object if I don't know the names?
I'm just using vanilla google apps script as I don't have any experience with Jquery etc...
Any help would be much appreciated!
I usually use a little helper function that looks like this:
var keyVal = function(o) {
var key = Object.keys(o)[0];
return {"key": key, "val":o[key]};
} ;
This will map an object with a variable key to a key/value object {key:...., val:{}}, which is usually convenient enough to work with.
describe.only ("stack overflow answer", function(){
it ("is should create a key/value pair" , function(){
var res = keyVal( {
"facebook.com": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
}});
res.key.should.equal('facebook.com');
res.val.attributes.should.deep.equal({});
});
Within the loop, the variable i contains the current key. Replacing the empty string with i should give you what you need.
You might also want to look at some of the more functional tools built into Javascript. Some more concise code might also be more explicit:
data.map(function(datum) {
var timep = datum.start_date == datum.end_date ? datum.end_date :
(data.start_date + "-" + datum.end_date);
return datum.SubRows.map(function(subRow) {
return Object.keys(subRow).map(function(key) {
return [timep, key, subRow[key].measures.Visits];
});
});
});
would return an object something like this:
[
[
[
["2013-12", "facebook.com", 100],
["2013-12", "google.co.uk", 100],
["2013-12", "newsnow.co.uk", 100],
["2013-12", "No Referrer", 100],
["2013-12", "t.co", 100 ]
]
]
]
This just uses map and Object.keys to simplify some of what you're doing with explicit loops.