Ramda - How to filter array with other array and multiple properties - ramda.js

I am trying to filter one array with another array using Ramda.
This is an array of edges for ELK algorithm. sources and targets are arrays but in this case, those arrays have always single value.
const edges = [
{
"id": "47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#2789a940-15d1-4ff0-b2ef-9f6cde676c18",
"sources": [
"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"
],
"targets": [
"2789a940-15d1-4ff0-b2ef-9f6cde676c18"
]
},
{
"id": "47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#7cf88eab-5da4-492b-839c-30916fa98fb9",
"sources": [
"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"
],
"targets": [
"7cf88eab-5da4-492b-839c-30916fa98fb9"
]
},
{
"id": "fefd95e0-11d0-44f6-9b48-ec2a0ea1b328#53a6d558-c97b-42df-af69-cf27912dd158",
"sources": [
"fefd95e0-11d0-44f6-9b48-ec2a0ea1b328"
],
"targets": [
"53a6d558-c97b-42df-af69-cf27912dd158"
]
}
]
This is an example of a second array that I would like to use to filter values from the first one - id corresponds to the value in sources and outgoingNodeId corresponds to the value in targets from the first array.
const selectedNodes = [
{
"id": "47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada",
"outgoingNodeId": "2789a940-15d1-4ff0-b2ef-9f6cde676c18",
"groupId": "27",
"sectionId": "0e09e7dd-0f71-48a1-a843-36d8e85574b3"
},
{
"id": "fefd95e0-11d0-44f6-9b48-ec2a0ea1b328",
"outgoingNodeId": "53a6d558-c97b-42df-af69-cf27912dd158",
"groupId": "27",
"sectionId": "0e09e7dd-0f71-48a1-a843-36d8e85574b3"
}
]
In this case, result should look like this:
const result = [
{
"id": "47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#2789a940-15d1-4ff0-b2ef-9f6cde676c18",
"sources": [
"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"
],
"targets": [
"2789a940-15d1-4ff0-b2ef-9f6cde676c18"
]
},
{
"id": "fefd95e0-11d0-44f6-9b48-ec2a0ea1b328#53a6d558-c97b-42df-af69-cf27912dd158",
"sources": [
"fefd95e0-11d0-44f6-9b48-ec2a0ea1b328"
],
"targets": [
"53a6d558-c97b-42df-af69-cf27912dd158"
]
}
]
I tried few things but to be honest I don't have anything worth showing here.
My last try was around:
R.pipe(
R.groupBy(R.prop('sources'))
)(edges)
And then filtering using R.filter(R.compose(R.flip(R.contains)(selectedNodesIds), R.prop('id')))
But I need to filter not only sources but also targets
Any help would be appreciated. Thank you
EDIT:
Another example for a different approach:
const edges = [
{
"id": "47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#2789a940-15d1-4ff0-b2ef-9f6cde676c18",
"sources": [
"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"
],
"targets": [
"2789a940-15d1-4ff0-b2ef-9f6cde676c18"
]
},
{
"id": "47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#7cf88eab-5da4-492b-839c-30916fa98fb9",
"sources": [
"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"
],
"targets": [
"7cf88eab-5da4-492b-839c-30916fa98fb9"
]
},
{
"id": "fefd95e0-11d0-44f6-9b48-ec2a0ea1b328#53a6d558-c97b-42df-af69-cf27912dd158",
"sources": [
"fefd95e0-11d0-44f6-9b48-ec2a0ea1b328"
],
"targets": [
"53a6d558-c97b-42df-af69-cf27912dd158"
]
},
...multipleObjectsHere
]
selectedNodes contains only one object:
const selectedNodes = [
{
"id": "47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada",
"outgoingNodeId": "2789a940-15d1-4ff0-b2ef-9f6cde676c18",
"groupId": "27",
"sectionId": "0e09e7dd-0f71-48a1-a843-36d8e85574b3"
}
]
As a result, we get:
const result = [
{
"id": "47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#2789a940-15d1-4ff0-b2ef-9f6cde676c18",
"sources": [
"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"
],
"targets": [
"2789a940-15d1-4ff0-b2ef-9f6cde676c18"
]
},
{
"id": "fefd95e0-11d0-44f6-9b48-ec2a0ea1b328#53a6d558-c97b-42df-af69-cf27912dd158",
"sources": [
"fefd95e0-11d0-44f6-9b48-ec2a0ea1b328"
],
"targets": [
"53a6d558-c97b-42df-af69-cf27912dd158"
]
},
...multipleObjectsHere
]
To sum up, if something is not in selectedNodes we don't filter it out and leave it as a result

Use R.innerJoin to create an intersection between 2 arrays using a comparison function:
const { innerJoin, includes } = R
const fn = innerJoin(({ sources, targets }, { id, outgoingNodeId }) =>
includes(id, sources) && includes(outgoingNodeId, targets)
)
const edges = [{"id":"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#2789a940-15d1-4ff0-b2ef-9f6cde676c18","sources":["47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"],"targets":["2789a940-15d1-4ff0-b2ef-9f6cde676c18"]},{"id":"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#7cf88eab-5da4-492b-839c-30916fa98fb9","sources":["47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"],"targets":["7cf88eab-5da4-492b-839c-30916fa98fb9"]},{"id":"fefd95e0-11d0-44f6-9b48-ec2a0ea1b328#53a6d558-c97b-42df-af69-cf27912dd158","sources":["fefd95e0-11d0-44f6-9b48-ec2a0ea1b328"],"targets":["53a6d558-c97b-42df-af69-cf27912dd158"]}]
const selectedNodes = [{"id":"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada","outgoingNodeId":"2789a940-15d1-4ff0-b2ef-9f6cde676c18","groupId":"27","sectionId":"0e09e7dd-0f71-48a1-a843-36d8e85574b3"},{"id":"fefd95e0-11d0-44f6-9b48-ec2a0ea1b328","outgoingNodeId":"53a6d558-c97b-42df-af69-cf27912dd158","groupId":"27","sectionId":"0e09e7dd-0f71-48a1-a843-36d8e85574b3"}]
const result = fn(edges, selectedNodes)
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
The other logic should be see if the id is included in sources. If it is also check the outgoingNodeId. If not, return true.
const { innerJoin, includes } = R
const fn = innerJoin(({ sources, targets }, { id, outgoingNodeId }) =>
!includes(id, sources) || includes(outgoingNodeId, targets)
)
const edges = [{"id":"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#2789a940-15d1-4ff0-b2ef-9f6cde676c18","sources":["47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"],"targets":["2789a940-15d1-4ff0-b2ef-9f6cde676c18"]},{"id":"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada#7cf88eab-5da4-492b-839c-30916fa98fb9","sources":["47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada"],"targets":["7cf88eab-5da4-492b-839c-30916fa98fb9"]},{"id":"fefd95e0-11d0-44f6-9b48-ec2a0ea1b328#53a6d558-c97b-42df-af69-cf27912dd158","sources":["fefd95e0-11d0-44f6-9b48-ec2a0ea1b328"],"targets":["53a6d558-c97b-42df-af69-cf27912dd158"]}]
const selectedNodes = [{"id":"47c0ffd2-6a2c-4e7f-9fd9-0e1207225ada","outgoingNodeId":"2789a940-15d1-4ff0-b2ef-9f6cde676c18","groupId":"27","sectionId":"0e09e7dd-0f71-48a1-a843-36d8e85574b3"}]
const result = fn(edges, selectedNodes)
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Related

How to add a tooltip to a d3.js path?

I am building a floormap from geojson data using d3.js topo and d3.js path. For clarification, I am also using vue.js. I want to add a tooltip when the user hovers over a room (aka the d3.js path). First I added just a console log to when the user hovers over a path but that did not work. I noticed that every time I load the app it does a console log but not when a user clicks / hovers over the d3.js path. I heard someone say that I would have to create an invisible circle or rectangle which would have the tooltip property bind to it but I don't think that route would work once the floor maps get complex. I dont care about adding any actual data to the tooltip right now but later I would want to. Can someone point me in the right direction, please? Thank you.
const data = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
0, 0
],
[
0, 11.4
],
[
7, 11.4
],
[
7, 0
],
[
0, 0
]
]
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
7, 0
],
[
7, 11.4
],
[
12, 11.4
],
[
12, 0
],
[
7, 0
]
]
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
12, 0
],
[
12, 11.4
],
[
19, 11.4
],
[
19, 0
],
[
12, 0
]
]
]
}
}
]
};
var svg = d3.select("svg")
var width = +svg.attr("width")
var height = +svg.attr("height")
svg.attr("transform", "translate(" + width / 2 + ",0)")
var projection = d3.geoIdentity().fitSize([width, height], data)
var path = d3.geoPath(projection)
svg.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d", path)
.attr("fill", "grey")
.attr("fill-opacity", .2)
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.on("mouseover", console.log("hello"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="200"></svg>
There are several ways to do this. The simplest - but most limited - one is to create a <title> element in each path. It will show on hover, as it's browser native.
const data = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": { "name": "Kitchen" },
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
0, 0
],
[
0, 11.4
],
[
7, 11.4
],
[
7, 0
],
[
0, 0
]
]
]
}
},
{
"type": "Feature",
"properties": { "name": "Living room" },
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
7, 0
],
[
7, 11.4
],
[
12, 11.4
],
[
12, 0
],
[
7, 0
]
]
]
}
},
{
"type": "Feature",
"properties": { "name": "Toilet" },
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
12, 0
],
[
12, 11.4
],
[
19, 11.4
],
[
19, 0
],
[
12, 0
]
]
]
}
}
]
};
var svg = d3.select("svg")
var width = +svg.attr("width")
var height = +svg.attr("height")
svg.attr("transform", "translate(" + width / 2 + ",0)")
var projection = d3.geoIdentity().fitSize([width, height], data)
var path = d3.geoPath(projection)
svg.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d", path)
.attr("fill", "grey")
.attr("fill-opacity", .2)
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.append("title")
// Do this to maintain access to the features you drew
.datum(function(d) { return d; })
.text(function(d) {
return d.properties.name;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="200"></svg>
More complicated ones can use an absolutely positioned div, for example:
const data = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"name": "Kitchen"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
0, 0
],
[
0, 11.4
],
[
7, 11.4
],
[
7, 0
],
[
0, 0
]
]
]
}
},
{
"type": "Feature",
"properties": {
"name": "Living room"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
7, 0
],
[
7, 11.4
],
[
12, 11.4
],
[
12, 0
],
[
7, 0
]
]
]
}
},
{
"type": "Feature",
"properties": {
"name": "Toilet"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
12, 0
],
[
12, 11.4
],
[
19, 11.4
],
[
19, 0
],
[
12, 0
]
]
]
}
}
]
};
var svg = d3.select("svg")
var width = +svg.attr("width")
var height = +svg.attr("height")
var tooltip = d3.select("#tooltip")
svg.attr("transform", "translate(" + width / 2 + ",0)")
var projection = d3.geoIdentity().fitSize([width, height], data)
var path = d3.geoPath(projection)
svg.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d", path)
.attr("fill", "grey")
.attr("fill-opacity", .2)
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.on("mousemove", function(d) {
// +3 as some offset to make sure spawning the tooltip doesn't
// accidentally also cause unhover and thus removing itself
tooltip
.html(d.properties.name)
.style("display", "block")
.style("left", d3.event.x + 3 + 'px')
.style("top", d3.event.y + 3 + 'px');
})
.on("mouseleave", function() {
tooltip
.style("display", null)
.style("left", null)
.style("top", null);
});
#tooltip {
position: absolute;
top: 0;
left: 0;
display: none;
border: solid 1px red;
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="200"></svg>
<div id="tooltip"></div>

Replace specific values in the array using dwl 1.0

Problem with using mapObject function properly.
Trying to retain existing array structure but calculate number of vehicles and properties and update the existing array that contains the value.
GENERAL data comes from one source, VEHICLE data comes from another source, PROPERTY data comes from another source. So when merging, I have to update GENERAL data with count of other source data.
Also GENERAL is an array object, it will always have 1. So using GENERAL[0] is safe and fine.
Original Payload
[
{
"commId": "1",
"GENERAL": [
{
"ID": "G1",
"VEHICLE_COUNT": "TODO",
"PROPERTY_COUNT": "TODO"
}
],
"VEHICLE": [
{
"ID": "V1-1"
},
{
"ID": "V1-2"
}
],
"PROPERTY": [
{
"ID": "P1-1"
}
]
},
{
"commId": "2",
"GENERAL": [
{
"ID": "G2",
"VEHICLE_COUNT": "TODO",
"PROPERTY_COUNT": "TODO"
}
],
"VEHICLE": [
{
"ID": "V2-1"
}
],
"PROPERTY": [
{
"ID": "P2-1"
},
{
"ID": "P2-2"
}
]
},
{
"commId": "3",
"GENERAL": [
{
"ID": "G3",
"VEHICLE_COUNT": "TODO",
"PROPERTY_COUNT": "TODO"
}
],
"VEHICLE": [
{
"ID": "V3-1"
},
{
"ID": "V3-2"
},
{
"ID": "V3-3"
}
]
}
]
Tried using map to loop through the payload and tried modifying 2 attribute but only managed to map one but even that is showing wrong output.
test map (item, index) -> {
(item.GENERAL[0] mapObject (value, key) -> {
(key): (value == sizeOf (item.VEHICLE)
when (key as :string) == "VEHICLE_COUNT"
otherwise value)
})
}
Expected output:
[
{
"commId": "1",
"GENERAL": [
{
"ID": "G1",
"VEHICLE_COUNT": "2",
"PROPERTY_COUNT": "1"
}
],
"VEHICLE": [
{
"ID": "V1-1"
},
{
"ID": "V1-2"
}
],
"PROPERTY": [
{
"ID": "P1-1"
}
]
},
{
"commId": "2",
"GENERAL": [
{
"ID": "G2",
"VEHICLE_COUNT": "1",
"PROPERTY_COUNT": "2"
}
],
"VEHICLE": [
{
"ID": "V2-1"
}
],
"PROPERTY": [
{
"ID": "P2-1"
},
{
"ID": "P2-2"
}
]
},
{
"commId": "3",
"GENERAL": [
{
"ID": "G3",
"VEHICLE_COUNT": "3",
"PROPERTY_COUNT": "0"
}
],
"VEHICLE": [
{
"ID": "V3-1"
},
{
"ID": "V3-2"
},
{
"ID": "V3-3"
}
]
}
]
Getting totally wrong output so far:
[
{
"ID": "G1",
"VEHICLE_COUNT": false,
"PROPERTY_COUNT": "TODO"
},
{
"ID": "G2",
"VEHICLE_COUNT": false,
"PROPERTY_COUNT": "TODO"
},
{
"ID": "G3",
"VEHICLE_COUNT": false,
"PROPERTY_COUNT": "TODO"
}
]
Edited: Update for dynamic transform
The below dataweave transform is not particularly attractive, but it might work for you.
Thanks to Christian Chibana for helping me find a dynmaic answer by answering this question: Why does Mule DataWeave array map strip top level objects?
%dw 1.0
%output application/json
---
payload map ((item) ->
(item - "GENERAL") ++
GENERAL: item.GENERAL map (
$ - "VEHICLE_COUNT"
- "PROPERTY_COUNT"
++ { VEHICLE_COUNT: sizeOf (item.VEHICLE default []) }
++ { PROPERTY_COUNT: sizeOf (item.PROPERTY default []) }
)
)
It is dynamic, so everything should be copied across as it comes in, with only the two fields you want being updated.
The output for this transform with the input you supplied is below. Only difference from your desired is that the counts are shown as numbers rather than strings. If you really need them as strings you can cast them like (sizeOf (comm.VEHICLE default [])) as :string,
[
{
"commId": "1",
"VEHICLE": [
{
"ID": "V1-1"
},
{
"ID": "V1-2"
}
],
"PROPERTY": [
{
"ID": "P1-1"
}
],
"GENERAL": [
{
"ID": "G1",
"VEHICLE_COUNT": 2,
"PROPERTY_COUNT": 1
}
]
},
{
"commId": "2",
"VEHICLE": [
{
"ID": "V2-1"
}
],
"PROPERTY": [
{
"ID": "P2-1"
},
{
"ID": "P2-2"
}
],
"GENERAL": [
{
"ID": "G2",
"VEHICLE_COUNT": 1,
"PROPERTY_COUNT": 2
}
]
},
{
"commId": "3",
"VEHICLE": [
{
"ID": "V3-1"
},
{
"ID": "V3-2"
},
{
"ID": "V3-3"
}
],
"GENERAL": [
{
"ID": "G3",
"VEHICLE_COUNT": 3,
"PROPERTY_COUNT": 0
}
]
}
]

Create a new Google Sheet with row or column groups

I'm trying to create a new spreadsheet using spreadsheets#create, with specified row groups.
In the API Explorer, I am entering in the JSON below. which corresponds to the following appearance:
No errors are flagged or returned when I execute the call, but when the sheet is created, the specified grouping is not created - only the values are set.
{ "properties": {
"title": "Test Spreadsheet",
"locale": "en"
},
"sheets": [
{ "properties": {"title": "Test1"},
"data": [
{
"startRow": 0,
"startColumn": 0,
"rowData": [
{ "values": [
{ "userEnteredValue": { "stringValue": "Top1" } }
]
},
{ "values": [
{ "userEnteredValue": { "stringValue": "Top2" } }
]
},
{ "values": [
{ "userEnteredValue": { "stringValue": "" } },
{ "userEnteredValue": { "stringValue": "Top2A" } }
]
},
{ "values": [
{ "userEnteredValue": { "stringValue": "" } },
{ "userEnteredValue": { "stringValue": "Top2B" } }
]
},
{ "values": [
{ "userEnteredValue": { "stringValue": "" } },
{ "userEnteredValue": { "stringValue": "Top2C" } }
]
},
{ "values": [
{ "userEnteredValue": { "stringValue": "Top3" } }
]
}
]
}
],
"rowGroups": [
{ "range": {
"dimension": "ROWS",
"startIndex": 2,
"endIndex": 5
}
}
]
}
]
}
Even when I create the rowGroups JSON directly on the page, with its structured editor to make sure it is properly defined, the created spreadsheet still doesn't group the specified rows. I have triple-checked all my objects from the top down, and can't see what I am doing wrong.

How to get values in an array from nested array of objects based on a given condition?

I'm using lodash and I have the following array of objects:
[{
"id": 1,
"values": [
{
"sub": "fr",
"name": "foobar1"
},
{
"sub": "en",
"name": "foobar2"
}
]
},
{
"id": 2,
"values": [
{
"sub": "fr",
"name": "foobar3"
},
{
"sub": "en",
"name": "foobar4"
}
]
}]
What i'm trying to get the list of ID and name for a given "SUB".
So, with the previous object, if I send the sub fr I want to get:
[{
"id": 1,
"name": "foobar1"
},
{
"id": 2,
"name": "foobar3"
}]
Do you know if I can easily do it with lodash?
I tried to use _.pick but it doesn't working(I'm a bit lost with these mixes between nested objects and arrays) _.map(data, function (o) { _.pick(o, ['id', 'values.name']) });.
I also tried to use _.filter with things like _.filter(data, { values: [{ sub: 'fr' }]}); but it return all the items. What I'm looking for is to return the nested part only.
You can use flatMap() where its callback returns an array of filtered subs using filter() where each filtered item is transformed using map().
var result = _.flatMap(data, item =>
_(item.values)
.filter({ sub: 'fr' })
.map(v => ({id: item.id, name: v.name}))
.value()
);
var data = [{
"id": 1,
"values": [
{
"sub": "fr",
"name": "foobar1"
},
{
"sub": "en",
"name": "foobar2"
}
]
},
{
"id": 2,
"values": [
{
"sub": "fr",
"name": "foobar3"
},
{
"sub": "en",
"name": "foobar4"
}
]
}];
var result = _.flatMap(data, item =>
_(item.values)
.filter({ sub: 'fr' })
.map(v => ({id: item.id, name: v.name}))
.value()
);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.12.0/lodash.js"></script>
following #ryeballar's answer, a shorter version with map
var result = _.map(data, item => ({id: item.id, name: item.name}));

How to change the legend values

Generally I am having a hard time understanding how to construct the data item that i feed into the parseRawData. But here I am having a much simpler problem...I am unable to change the chart legend. I created this visualization:
var appRetentionAndroidFunnelQry = new Keen.Query("funnel", {
steps: [
{
event_collection: "devices",
actor_property: "activationCode",
filters: [
{
"property_name": "action",
"operator": "eq",
"property_value": "Create"
},
{
"property_name": "platform",
"operator": "eq",
"property_value": "android"
}
],
timeframe: {
"start": periodRefStart.toISOString(),
"end": periodRefEnd.toISOString()
}
},
{
event_collection: "devices",
actor_property: "activationCode",
filters: [
{
"property_name": "action",
"operator": "eq",
"property_value": "Update"
},
{
"property_name": "platform",
"operator": "eq",
"property_value": "android"
}
],
timeframe: {
"start": period1Start.toISOString(),
"end": period1End.toISOString()
}
},
{
event_collection: "devices",
actor_property: "activationCode",
filters: [
{
"property_name": "action",
"operator": "eq",
"property_value": "Update"
},
{
"property_name": "platform",
"operator": "eq",
"property_value": "android"
}
],
timeframe: {
"start": period2Start.toISOString(),
"end": period2End.toISOString()
}
}
]
});
var appRetentionIosFunnelQry = new Keen.Query("funnel", {
steps: [
{
event_collection: "devices",
actor_property: "activationCode",
filters: [
{
"property_name": "action",
"operator": "eq",
"property_value": "Create"
},
{
"property_name": "platform",
"operator": "eq",
"property_value": "ios"
}
],
timeframe: {
"start": periodRefStart.toISOString(),
"end": periodRefEnd.toISOString()
}
},
{
event_collection: "devices",
actor_property: "activationCode",
filters: [
{
"property_name": "action",
"operator": "eq",
"property_value": "Update"
},
{
"property_name": "platform",
"operator": "eq",
"property_value": "ios"
}
],
timeframe: {
"start": period1Start.toISOString(),
"end": period1End.toISOString()
}
},
{
event_collection: "devices",
actor_property: "activationCode",
filters: [
{
"property_name": "action",
"operator": "eq",
"property_value": "Update"
},
{
"property_name": "platform",
"operator": "eq",
"property_value": "ios"
}
],
timeframe: {
"start": period2Start.toISOString(),
"end": period2End.toISOString()
}
}
]
});
var steps = [
periodRefStart.toISOString().slice(0, 10) + ' - ' + periodRefEnd.toISOString().slice(0, 10),
period1Start.toISOString().slice(0, 10) + ' - ' + period1End.toISOString().slice(0, 10),
period2Start.toISOString().slice(0, 10) + ' - ' + period2End.toISOString().slice(0, 10)
];
var combinedFunnel = new Keen.Dataviz()
.el(document.getElementById('app-retention-chart'))
.chartType('columnchart')
.chartOptions({
orientation: 'horizontal'
})
.height(250)
.prepare(); // start spinner
client.run([appRetentionAndroidFunnelQry, appRetentionIosFunnelQry], function (err, response) {
var output = {
result: [],
steps: []
};
// Combine results
Keen.utils.each(response[0].result, function (stepResult, i) {
output.result.push([
steps[i],
response[0].result[i],
response[1].result[i]
]);
});
// Draw custom data object
combinedFunnel
.parseRawData(output)
.render();
});
The output looks like this:
How can I please change the legend and the column labels to say Android and iOS instead of 1 and 2? Also...any help in better understanding how the data parser works will be appreciated. I tried reading the parseRawData.js source code but it seems it is beyond my not-so-great JavaScript ability.
Regards,
Khaled
In the very last piece of your code, you can choose what the labels are:
// Draw custom data object
combinedFunnel
.parseRawData(output)
.labels(["Android", "iOS"])
.render();
I got this from: https://github.com/keen/keen-js/blob/master/docs/visualization.md#funnels
Ok, so I played around with this a bit, and to get what you want, you're going to have to completely over ride the dataset that gets passed to the Dataviz component.
Here is an example jsfiddle that shows you the format for the data to get what you're looking for:
http://jsfiddle.net/hex337/16av86as/2/
The key component is this:
.call(function () {
this.dataset.output([
["index", "iOS", "Android"],
["r1", 1000, 900],
["r2", 750, 700]
]);
})
Instead of hard coding the numbers, you'll want to use the results from the queries that you run, but this should give you the "iOS" and "Android" legend keys, and you can set the "r1" and "r2" to be the steps in your funnel.
I hope this solves the problem!
You can use the .labelMapping() function. Especially when using grouping for the data the order of labels in the data can change, so .labelMapping() is safer than just setting labels with .labels().
chart.data(res).labelMapping({
'741224f021ca7f': 'Sensor A',
'a1a9e6253e16af': 'Sensor B'
}).render();