NVD3.js coloring specific bars in graph - svg

Is there a way to color specific bars? If a bar is less than the line, color it red.
Code: https://github.com/tvinci/webs/blob/gh-pages/lineplusbar.html
Example: http://tvinci.github.io/webs/lineplusbar.html
I'd like to do something like this but the value for i is not the y value that I send in. It has been modified:
d3.selectAll("rect.nv-bar")
.style("fill", function(d, i){
return i > 50 ? "red":"blue";
});
Data:
var overview_data=[
{
"key" : "Achieved",
"bar": true,
"values" : [ [ "1x" , 30] , [ "2x" , 70] , [ "3x" , 200] ]
},
{
"key" : "Required",
"values" : [ [ "1x" , 50] , [ "2x" , 100] , [ "3x" , 150] ]
}
].map(function(series) {
series.values = series.values.map(function(d) { return {x: d[0], y: d[1] } });
return series;
});

You can do:
d3.selectAll("rect.nv-bar")
.style("fill", function(d, i){
return d.y > 50 ? "red":"blue";
});

The upward answer is right but it won't work when you change the data dynamically in my-case.
You can use the following configuration to get different bar colors based on the value.
var data=[ {
"key": "data",
"values": [
{
"x": 1,
"y": 20
},
{
"x": 2,
"y": 15
},
{
"x": 3,
"y": 85
},
{
"x": 4,
"y": 66
},}];
nv.addGraph(function() {
var chart = nv.models.multiBarChart()
.barColor(getColorArrayForGraph())
.transitionDuration(350)
.reduceXTicks(true)
.rotateLabels(0)
.showControls(true)
.groupSpacing(0.1);
chart.xAxis
.tickFormat(d3.format(',f'));
chart.yAxis
.tickFormat(d3.format(',.1f'));
d3.select('#chart1 svg')
.datum(data)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
}
// GET color array based on the value
function getColorArrayForGraph() {
let colorArray: any = [];
for (let item of data) {
if (item.y > 50) {
colorArray.push('#FF0000');
} else {
colorArray.push('#004c00');
}
}
return colorArray;
};
So here, barcolor(["#FF0000","#00FFCC",....]) function takes arguments as array of colors.
This way you can get the array of colors and use it into barcolor().

Related

CouchDB display distinct values

I have a document like below,
{
"id": "7d9fdc2f4846544d62da3421bf011b31",
"al": [
{ "id16": "0x1d42",
"pos": {
"x": 10.32,
"y": 11.13,
"z": 1.22
},
"resultTime": "2020-06-01T20:45:34.976Z"
},
{ "id16": "0x1342",
"pos": {
"x": 0.32,
"y": 1.13,
"z": 13.22
},
"resultTime": "2021-06-01T20:45:34.976Z"
}
.
.
.
],
"Timestamp": 272179,
"Oid": "Onion1",
}
and Design document is like below
{
"id": "_design/GetALwithAnchorID",
"key": "_design/GetALwithAnchorID",
"value": {
"rev": "32-6db6c4e105336d47a6c8e7e8458ee345"
},
"doc": {
"_id": "_design/GetALwithAnchorID",
"_rev": "32-6db6c4e105336d47a6c8e7e8458ee345",
"views": {
"GetALwithAnchorID": {
"map": "function (doc) {\n\n for (var i=0; i<doc.al.length; i++) { \n emit(doc.al[i].id16, doc.al[i].pos);\n }\n \n}\n\n",
"reduce": "_approx_count_distinct"
}
},
"language": "javascript"
}
}
when I query the view like
http://127.0.0.1:5984/rtls/_design/GetALwithAnchorID/_view/GetALwithAnchorID?group_level=1&key=%220x1d42%22
I get the results as below
{"rows":[
{"key":"0x1d42","value":1}
]}
But I want distinct values of id16 and pos of id16. and to sort these distinct values by time and display the values of pos instead of "value":1 when Iquery?
thank you in advance.
OK so not quite the same as this similar answer. Anyone coming across this Q/A, I recommend reading over that answer.
Consider the following emit for your given doc structure:
doc.al.forEach(e => emit(
[e.pos.x, e.pos.y, e.pos.z, e.resultTime], // key
[e.id16, e.pos, e.resultTime]) // value
));
The emit's complex key visualized in the index (loosely not verbatim):
[-3,-2,-1,"2017-10-28T22:56:58.852Z"]
[-3,-2,-1,"2019-01-23T03:33:20.958Z"] **
. . .
[0,0,0,"2016-05-27T01:38:36.305Z"]
[0,0,0,"2016-12-27T05:17:02.255Z"] **
. . .
[1,2,3,"2016-11-14T17:31:59.468Z"]
[1,2,3,"2017-07-17T07:52:38.180Z"] **
Where each ** the last item in the pos group and significantly the most recent resultTime. All due to CouchDB's collation.
Working with CouchDB demands understanding the B-tree, and it's documentation has a great rundown of it in its Reduce/Rereduce documentation.
Now consider this reduce function:
function(keys,values,rereduce) {
return values[0];
}
It doesn't look terribly impressive, but further consider calling the view with these parameters:
{
reduce: true,
group_level: 1,
descending: true
}
By reversing the order of the index scan with descending the reduce function is guaranteed to return the most recent row with respect to resultTime of any given pos group.
Here's a simple demo using pouchDB. It generates 6 documents with random resultTime's and randomly selects pos from a pool of 3. Have a look at the design doc.
async function showReduceDocs(view) {
let result = await db.query(view, {
reduce: true,
group_level: 1,
descending: true
});
// show
debugger;
gel('view_reduce').innerText = result.rows.map(row => `${JSON.stringify(row.value)}`.split(',').join(', ')).join('\n');
return result;
}
async function showViewDocs(view) {
let result = await db.query(view, {
reduce: false,
include_docs: false
});
//show
gel('view_docs').innerText = result.rows.map(row => JSON.stringify(row.key))
.join('\n');
}
function getDocsToInstall(count) {
// design document
const ddoc = {
"_id": "_design/SO-66231293",
"views": {
"id16": {
"map": `function (doc) {
doc.al.forEach((e) => emit([e.pos.x, e.pos.y, e.pos.z, e.resultTime],[e.id16, e.pos, e.resultTime]));
}`,
"reduce": `function(keys,values,rereduce) {
return values[0];
}`
}
}
};
// create a set of random documents.
let docs = new Array(count);
let docId = 65;
const posSeed = [{
x: 0,
y: 0,
z: 0
},
{
x: 1,
y: 2,
z: 3
},
{
x: -3,
y: -2,
z: -1
}
];
const dateSeed = [new Date(2000, 0, 1), new Date(), 0, 24];
while (count--) {
let n = 6;
let doc = {
_id: String.fromCharCode(docId++),
al: new Array(n)
};
while (n-- > 0) {
doc.al[n] = {
"id16": "0x000" + n,
"pos": posSeed[Math.floor(Math.random() * 100) % 3],
"resultTime": randomDate(...dateSeed).toISOString()
};
}
docs[count] = doc;
}
docs.push(ddoc);
return docs;
}
const db = new PouchDB('SO-66231293', {
adapter: 'memory'
});
(async() => {
// install docs and show view in various forms.
await db.bulkDocs(getDocsToInstall(6));
gel('content').classList.remove('hide')
showReduceDocs('SO-66231293/id16');
showViewDocs('SO-66231293/id16');
})();
const gel = id => document.getElementById(id);
/*
https://stackoverflow.com/questions/31378526/generate-random-date-between-two-dates-and-times-in-javascript/31379050#31379050
*/
function randomDate(start, end, startHour, endHour) {
var date = new Date(+start + Math.random() * (end - start));
var hour = startHour + Math.random() * (endHour - startHour) | 0;
date.setHours(hour);
return date;
}
<script src="https://cdn.jsdelivr.net/npm/pouchdb#7.1.1/dist/pouchdb.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<div id='content' class='hide'>
<div>View: reduce</div>
<pre id='view_reduce'></pre>
<hr/>
<div>View: complex key</div>
<pre id='view_docs'></pre>
</div>
Edit
Amended the demo snippet according to OP's comments.

Remove square bracket([ ]) in json file node JS

I want to remove square bracket "[ ]" in json object or concatenate some array of object to one array of object.
This code for counting current stock
currentStock : ( req, res, next) => {
var listed_item = _(arr_items).map( (z) => {
var result = module.exports.getTransactionAfterDate(z.created_at, z.item_id);
return result;
})
var jsonContent = JSON.stringify(listed_item);
return res.send(jsonContent);
},
getTransactionAfterDate : (date, item_id) => {
var result = _(db_transaction).filter( (x) => {
return x.created_at > date && x.item_id == item_id;
});
return result;
}
this is "listed_item" json result
[
[{
"id": 30608,
"item_id": "A01001",
"quantity": 150,
"status": "OUT",
"created_at": "2020-02-10 16:11:51",
}],
[],
[{
"id": 30412,
"item_id": "A02001",
"quantity": 53,
"status": "OUT",
"created_at": "2020-02-06 14:44:20",
}, {
"id": 30482,
"item_id": "A02001",
"quantity": 33,
"created_at": "2020-02-07 15:26:50",
"updated_at": "2020-02-07 15:26:50",
}]
]
what i want is like this
[
{
"id": 30608,
"item_id": "A01001",
"quantity": 150,
"status": "OUT",
"created_at": "2020-02-10 16:11:51",
},
{
"id": 30412,
"item_id": "A02001",
"quantity": 53,
"status": "OUT",
"created_at": "2020-02-06 14:44:20",
}, {
"id": 30482,
"item_id": "A02001",
"quantity": 33,
"created_at": "2020-02-07 15:26:50",
"updated_at": "2020-02-07 15:26:50",
}
]
note
i tried concatenate "getTransactionAfterDate" inside "arr_items" map to join all array inside
var temp = [];
var listed_item = _(arr_items).map( (z) => {
temp = _(temp).concat(module.exports.getTransactionAfterDate(z.created_at, z.item_id));
var result = temp;
return result;
})
but the result is empty array
[ [], [], [] ]
Try this
currentStock : ( req, res, next) => {
var items = [];
_(arr_items).forEach( (z) => {
var result = module.exports.getTransactionAfterDate(z.created_at, z.item_id);
result.forEach( item => {
items.push(item);
}
})
var jsonContent = JSON.stringify(items);
return res.send(jsonContent);
},
You can 'flatten' the array with lodash, which will just merge all the arrays into a single one:
_(arr).flatten()
That should then give you a single array of your objects, with no empty arrays either.
.flatten() in the lodash docs here: https://lodash.com/docs/#flatten
So it would be something like this (edited based on comments):
currentStock : ( req, res, next) => {
var listed_item = _(arr_items).map( (z) => {
var result = module.exports.getTransactionAfterDate(z.created_at, z.item_id);
return result;
})
var flattened = _(listed_item).flatten();
var jsonContent = JSON.stringify(flattened);
return res.send(jsonContent);
},
Nice and simple! (if I correctly understood what you are trying to do :-)
this can be done with the combination of _.flatten (for this lodash is required) and Array.prototype.filter like shown below
let newArray = _.flatten(listed_item.filter(e => e.length > 0))

Node-Red chart node with stored data

I have a problem with the chart node. I want to display a line graph, the data source is two MQTT messages. A microcontroller publishes two 2049 element byte arrays in 2 topics (linearccdL and linearccdH). The data are upper and lower bytes of words, and at first I have to join them, it works now.
When the 2049 pcs 16 bit numbers are ready i have to display them on a graph. According to the documentation, the graph node accepts arrays in the form:
[{
"series": ["A", "B", "C"],
"data": [
[{ "x": 1504029632890, "y": 5 },
{ "x": 1504029636001, "y": 4 },
{ "x": 1504029638656, "y": 2 }
],
[{ "x": 1504029633514, "y": 6 },
{ "x": 1504029636622, "y": 7 },
{ "x": 1504029639539, "y": 6 }
],
[{ "x": 1504029634400, "y": 7 },
{ "x": 1504029637959, "y": 7 },
{ "x": 1504029640317, "y": 7 }
]
],
"labels": [""]
}]
I'm using a function to format the data:
var rawDataL = new ArrayBuffer(2049);
var rawDataH = new ArrayBuffer(2049);
var rawData = new Uint16Array(2049);
var outobj = {};
var outarr = [];
var outmsg;
if (msg.topic == "rbmt/linearccdL") {
context.rawDataL = msg.payload;
context.Lset=true;
} else if (msg.topic == "rbmt/linearccdH") {
context.rawDataH = msg.payload;
context.Hset=true;
}
if (context.Lset & context.Hset) {
for (var i=0; i<2049; i++) {
rawData[i]=context.rawDataH[i]*256+context.rawDataL[i];
outobj={"x":i, "y":rawData[i]};
outarr.push(outobj);
}
context.Lset=false;
context.Hset=false;
outmsg = {"series":"[A]", data: outarr};
return outmsg;
}
After putting a debug node i can see the message, seems OK, but on the dashboard i get "No data".
I'm new in JS, can somebody help a bit please?
The debug message: debug msg

d3.js: zoom and drag failure

I'm using d3.js but I'm having a problem. When I zoom in SVG, is not commensurate. Also, after dragging, the handling of SVG I made; a further drifting. I'm giving the necessary code. What can I do to fix this code?
Code:
/* Tünelin bütün elemanları için bir dizi oluşturuldu
Daha sonra her ayrı eleman için dizi oluşturulup,
datalar bu doğrultuda çekilecektir.
*/
var tunnelElements = new Array();
tunnelElements = [{ "x": 0, "y": 0, "radius": 10, "color" : "green" },
{ "x": 25, "y": 25, "radius": 10, "color" : "green"},
{ "x": 50, "y": 50, "radius": 10, "color" : "green" },
{ "x": 75, "y": 75, "radius": 10, "color" : "green"},
{ "x": 100, "y": 100, "radius": 10, "color" : "green" },
{ "x": 125, "y": 125, "radius": 10, "color" : "green" },
{ "x": 62.5, "y": 62.5, "radius": 10, "color" : "red" },
{ "x": 0, "y": 125, "radius": 10, "color" : "purple" },
{ "x": 25, "y": 100, "radius": 10, "color" : "purple" },
{ "x": 50, "y": 75, "radius": 10, "color" : "purple" },
{ "x": 75, "y": 50, "radius": 10, "color" : "purple" },
{ "x": 100, "y": 25, "radius": 10, "color" : "purple" },
{ "x": 125, "y": 0, "radius": 10, "color" : "purple" }];
/* Tünel elemanları datadan çekilip circles dizisine
kopyalanmaktadır.
*/
var circles = []
for (var i = 0; i < tunnelElements.length; i++) {
circles[i] = tunnelElements[i];
};
console.log(circles);
var width = 719, height = 262;
var X = d3.scale.linear()
var Y = d3.scale.linear()
/* Semantic zoom için zoom değişkeni oluşturuldu */
var zoom = d3.behavior.zoom()
.x(X).y(Y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
/* Alternatif zoom
.on("zoom", function () {
circle.attr("transform", transform)
});
*/
/* Elementler svg olarak oluşturuldu */
var svg = d3.select("#d3")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom);
/* Road background çağırdık */
var road = svg.selectAll("image").data([0]);
road.enter()
.append("svg:image")
.attr("xlink:href", "http://ahmetates.com.tr/road.svg")
.attr("width", width)
.attr("height", height);
var circle;
/* Bütün elemenlar seçilip transform fonksiyonu
sayesinde drag and drop özelliği kazandı */
circle = svg.selectAll("circle")
.data(circles)
.enter().append("circle")
.attr("transform", transform);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
/* Fonksiyonlar */
function zoomed() {
road.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
circle.attr("transform", transform)
}
function transform(d) {
return "translate("+X(d.x)+", "+Y(d.y)+")";
}
function generateit(){
var circles = svg.selectAll("circle");
var json_circles = JSON.stringify(circles.data());
d3.select("#console").html('tunnelElements = '+json_circles+';');
}
d3.select("#exportit").on("click", generateit);
var circleAttributes = circle
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", function (d) { return d.radius; })
.style("fill", function(d) { return d.color; })
.call(drag);
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
#d3 svg{
margin:30px;
padding:10px;
border:1px solid #333;
width: 100%;
height: auto;
}
.line{
stroke-width: 3px;
}
#exportit {
background:#000;
cursor:pointer;
color:#fff;
width:45px;
padding:2px 4px;
}
<script src="http://d3js.org/d3.v3.min.js"></script>
<div id="exportit">Export</div>
<div id="d3"></div>
<div id="console"></div>
EDIT:
It seems that problem is not explained very well. The first problem can be traced like this: drag any circle, then drag background and you will see that dragged circle moved to random position. The second problem is scaling and you can trace that zooming in and out, then you can see that positions of circles are changed according to background.
You should make a main group and in that add all the components to which you want to give zoom and drag.
Something like this:
var maingroup = svg.append("g").call(zoom);
//add road and circles in this maingroup
//drag events on circle as you had done no change in that.
Here is a working fiddle this should give more clarity:
http://jsfiddle.net/cyril123/ow8r5n6f/3/

Transition between a circle and a line with svg and d3

JS Fiddle
I am trying to figure out how to "unfurl"/"unravel" a circle by "snipping" it at the top, and then it would animate to a line.
I have made a circle by using 16 points (and a 17th to close it since if I used a closed interpolation, the transition would look weird)
How (would it be via an animation?, just adjusting the x and y points?, another interpolation?) can you transition between the circle and the line one point at a time?
Circle points:
//The data for our line
var circleData = [ { "x": 150 , "y": 20 },
{ "x": 165.30, "y": 23.04},
{ "x": 178.28, "y": 31.71},
{ "x": 186.95, "y": 44.69},
{ "x": 190 , "y": 60 },
{ "x": 186.95, "y": 75.30},
{ "x": 178.28, "y": 88.28},
{ "x": 165.30, "y": 96.95},
{ "x": 150 , "y": 100 },
{ "x": 134.69, "y": 96.95},
{ "x": 121.71, "y": 88.28},
{ "x": 113.04, "y": 75.30},
{ "x": 110 , "y": 60.00},
{ "x": 113.04, "y": 44.69},
{ "x": 121.71, "y": 31.71},
{ "x": 134.69, "y": 23.04},
{ "x": 150 , "y": 20 } ];
Line Points:
var lineData = [ { "x": 10 , "y": 200 },
{ "x": 20 , "y": 200 },
{ "x": 30 , "y": 200 },
{ "x": 40 , "y": 200 },
{ "x": 50 , "y": 200 },
{ "x": 60 , "y": 200 },
{ "x": 70 , "y": 200 },
{ "x": 80 , "y": 200 },
{ "x": 90 , "y": 200 },
{ "x": 100 , "y": 200 },
{ "x": 110 , "y": 200 },
{ "x": 120 , "y": 200 },
{ "x": 130 , "y": 200 },
{ "x": 140 , "y": 200 },
{ "x": 150 , "y": 200 },
{ "x": 160 , "y": 200 },
{ "x": 170 , "y": 200 } ];
The first thing that I noticed when trying Duopixel's demo was that the circle seemed to shrink to fit on the line. Thus I decided to make the line the same length as the circle. Moreover to have a uniform distribution I wrote two functions to create the line and circle data arrays:
var numberOfPoints = 30;
var radius = 60
var margin = {top: 20,left: 20}
var lineLength = 2 * radius * Math.PI
var circleData = $.map(Array(numberOfPoints), function (d, i) {
var imag = margin.left + lineLength / 2 + radius * Math.sin(2 * i * Math.PI / (numberOfPoints - 1))
var real = margin.top + radius - radius * Math.cos(2 * i * Math.PI / (numberOfPoints - 1))
return {x: imag, y: real}
})
var lineData = $.map(Array(numberOfPoints), function (d, i) {
var y = margin.top + 2 * radius;
var x = margin.left + i * lineLength / (numberOfPoints - 1)
return { x: x, y: y}
}).reverse()
So, now, what effect can we apply? I will go with the easiest one: a transition mapping each point of the circle to its point on the line.
var circle = svgContainer.append("g")
.append("path")
.data([circleData])
.attr("d", lineFunction)
.attr("class", "circle")
.on("click", transitionToLine)
function transitionToLine() {
circle.data([lineData])
.transition()
.duration(1000)
.ease("linear")
.attr('d', lineFunction)
circle.on("click", transitionToCircle)
}
function transitionToCircle() {
circle.data([circleData])
.transition()
.duration(1000)
.ease("linear")
.attr('d', lineFunction)
circle.on("click", transitionToLine)
}
Here is the jsFiddle, you just have to click on the node to see the animation.
One important thing to notice is that the transition takes the same time for each point whereas in reality you would like the points in the end to arrive after the points near the middle. The trick you can use is to make the duration of the animation be proportional to the distance from the source point to the destination one but I don't see how to use it with lines as you pass the whole array so you cannot change the duration for a specific point.

Resources