CustomJs callbacks not updating data source for plot - javascript

I'm trying to update the data source of plot 2 based on the hovered overvalues of plot 1, so that an accompanying viz is created in plot 2 based on the data from plot 1.
Problem is I can get the data out of rect inside of the callback and format it into x and y for a line graph, but it does not update the plot only removes the placeholder values from it.
The data object contains the x and y values that need to be displayed in p2 (the line graph) and are equal length and formatted, not sure why its not changing the sourceLine ColumnDataSource
#hover tool for additional plots
p2 = figure(plot_width = 900, plot_height = 350)
#initial data source
sourceLine = ColumnDataSource(data = {'x': [2, 3, 5, 6, 8, 7], 'y': [6, 4, 3, 8, 7, 5]})
#line to be updated
ln = p2.line(x = 'x', y = 'y', line_width = 2, source = sourceLine.data)
#callback
callback = CustomJS(args={'rect': rc.data_source.data, 'line': ln.data_source}, code="""
var rdataYear = rect.YEAR;
var rdataAgeGroup = rect.AGE_GROUP;
var rdataDeaths = rect.DEATHS;
var data = {'x': [], 'y': []};
var deaths = [];
var years = [1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017
]
var indices = cb_data.index['1d'].indices;
var newarr = rect.AGE_GROUP.map((e,i) => e === rdataAgeGroup[indices] ? i : undefined).filter(x => x);
for (var i = 0; i < newarr.length; i++){
var index = newarr[i];
deaths.push(rdataDeaths[index])
}
//data that needs to be inside p2 line
data['x'].push(years)
data['y'].push(deaths)
line.data = data;
line.change.emit();
""")
#hover tool with the callback
p1.add_tools(HoverTool(tooltips=[('Year', '#YEAR'), ('Age Group', '#AGE_GROUP'),('Deaths', '#DEATHS'),('Rate', '#RATE')], callback=callback))
I want to have the output from the callback (data) update x and y of p2.line, im kinda new to python and bokeh is a bit funky so I would really appreciate some help :)

This line is incorrect:
ln = p2.line(x = 'x', y = 'y', line_width = 2, source = sourceLine.data)
The value of source should be the ColumnDataSource itself:
ln = p2.line(x = 'x', y = 'y', line_width = 2, source = sourceLine)
When you pass a dict like .data, Bokeh will create an internal CDS for you automatically, as a convenience, but now this new CDS that the glyph uses has nothing to do with the one you update in the callback.
Also, don't use the ['1d'] syntax for selection indices. It has has been deprecated now for a very long time, and will guaranteed be removed for good entirely before the end of this year. Instead:
var indices = cb_data.index.indices
Additionally, your JS is not actually updating the data source correctly. The operation at the end data['y'].push(deaths) actually makes the list of deaths a sublist of the existing empty list, which is not the correct format. Here is an updated version of the JS code:
const rdataYear = rect.YEAR
const rdataAgeGroup = rect.AGE_GROUP
const rdataDeaths = rect.DEATHS
const years = [1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017]
const indices = cb_data.index.indices;
const newarr = rect.AGE_GROUP.map((e,i) => e === rdataAgeGroup[indices] ? i : undefined).filter(x => x);
const deaths = []
for (var i = 0; i < newarr.length; i++){
deaths.push(rdataDeaths[newarr[i]])
}
line.data = {x: years, y: deaths}
Note if you actually assign to .data then Bokeh can detect that automatically, no need for an explicit signal.

Related

adding indexed values of arrays created in a forEach()

I am trying to add the values of multiple arrays (one starts out empty, but all the ones I am adding to it are the same length, though it be great if we could come up with something that adds them even if they are all different lengths) - not the sum of all values in each array, instead, sum of the values in the same index. For example:
array1 = [1, 2, 3]
array2 = [2, 3, 4]
desiredArray = [3, 5, 7]
The number of the arrays I will be adding is arbitrary, as they are created based on the users selection. (Based on the length of the array created from the selection). I want to sum the arrays by index to create a new array, and from the new array, I will create a decline curve. When I attempt to add them using "indexSum" I get an array below back full of NaaNs...though they are the correct legth:
requestedOil
requestedGas
requestedWater
These are the temporary arrays created by the length of the var "values" - these are the ones I am trying to add by respective index to eventually get the ones mentioned above:
Oil[well]
Gas[well]
Water[well]
THIS IS THE FUNCTON I CURRENTLY HAVE TO ADD ARRAYS AND CALLED WHEN USER MAKES SELECTION FROM multiple-site-selection
function updateCurves(){
var dropdownMenu = document.getElementById("multiple-site-selection").selectedOptions;
var values = Array.from(dropdownMenu).map(({ value }) => value);
console.log(values);
d3.json('./static/wellNames.json').then((data) => { //read in the wellNames.json file, which contains the array "names" with all the well names
wellOptions = data.names;
forSelection = wellOptions.map((x) => ({id:x}))
console.log(forSelection);
d3.json("./static/all_production.json").then((data) =>{
var requestedOil = [];
var requestedGas = [];
var requestedWater = [];
var site_date = [];
var Oil = [];
var Gas = [];
var Water = [];
values.forEach((well) => {
forSelection.forEach((pair) => {
if(well == Object.values(pair)){
Oil[well] = new Array();
Gas[well] = new Array();
Water[well] = new Array();
new Promise ((resolve) => data.forEach((site) => {
if(values.length == 1 && well == site[0]){
requestedOil.push(site[2]);
requestedGas.push(site[3]);
requestedWater.push(site[4]);
site_date.push(site[8])}
else if(values.length > 1 && well == site[0]){
indexSum = (a1, a2) => a1.map((v, i) => i + a2[v]);
Oil[well].push(site[2])
requestedOil = indexSum(Oil[well], requestedOil);
Gas[well].push(site[3])
requestedGas = indexSum(Gas[well], requestedGas);
Water[well].push(site[4])
requestedWater = indexSum(Water[well], requestedWater);
site_date.push(site[8])}
else{}
resolve()}))//PROMISE CLOSED
} //IF CLOSED
})//forSelection (dic containing names of well selected) closed
console.log(Oil[well]);
}); //values.forEach closed
THIS CODE CURRENTLY WORKS AS I AM NOT ADDING ANY ARRAYS AND IT IS CALLED AS SOON AS THE WEBPAGE LOADS
//FUNCTION TO CREATE DROP DOWN VALUES
function createDropdownOptions() {
var selector = d3.select("#multiple-site-selection"); //select dropdown <select> in well.html with id:"siteSelection"
d3.json('./static/wellNames.json').then((data) => { //read in the wellNames.json file, which contains the array "names" with all the well names
var wellOptions = data.names;
wellOptions.forEach((well) => {
selector
.append('option')
.text(well)
.property('Value', well);
})
})
};
createDropdownOptions(); //CALL FUNCTION TO CREATE DROPDOWN MENU VALUES
// //FUNCTION TO CREATE HOME/SUMMARY CURVES
function curvesHome() {
d3.json("./static/all_production.json").then((data) =>{ //THIS WORKS!!!
var site_oil = [];
var site_gas = [];
var site_water = [];
summarySiteDate = [];
new Promise ((resolve) => data.forEach(site => {if (site[0]==="Summary") {
site_oil.push(site[2]);
site_gas.push(site[3]);
site_water.push(site[4]);
summarySiteDate.push(site[8]);
} resolve()}));
//CALL FUNCTION TO CREATE DROPDOWN MENU VALUES
var mostRecentEntry = summarySiteDate[0]; //MOST RECENT DATE WITHOUT HOUR AS VARIABLE
var addingHours = "T00:00"; //HOURS TO ADD TO MOST RECENT DATE - NEEDED TO NORMALIZE FROM ORIGINAL 19 HOUR FORMAT
var nextYear = mostRecentEntry.concat(addingHours); //DATE AND HOUR AS SINGLE VARIABLE TO MAKE INTO DATE
var mostRecentDate = new Date(nextYear); //MAKE VARIABLE INTO DATE
var nextYearsDate = new Date(mostRecentDate.setFullYear(mostRecentDate.getFullYear() + 1)); //GET YEAR FROM MOST RECENT DATE AND ADD A YEAR
var nextYear= nextYearsDate.getFullYear() //GET NEXT YEARS DATE
var nextMonth= nextYearsDate.getMonth() + 1 // GET NEXTS YEARS MONTH, ADD ONE BECAUSE MONTHS ARE INDEXED AT 0
var nextDate= nextYearsDate.getDate() //GET NEXT YEARS DATE
nextYearGraph = `${nextYear}-${nextMonth}-${nextDate}`; // CREATE FULL DATE FOR NEXT YEAR IN RIGHT FORMAT FOR AXIS
console.log(`${nextYearGraph} is a year from the most recent production date. This is from curvesHome()`);
var dataOil = [{
x: summarySiteDate,
y: site_oil,
type: "line",
line:
{color: "green"}
}];
var layoutOil = {
title: "Oil BBL",
yaxis: {
type: 'log',
autorange: true
},
xaxis: {
autorange: false,
range: [summarySiteDate[summarySiteDate.length-1], nextYearGraph]
}
};
Plotly.newPlot("oilDeclineCurve", dataOil, layoutOil);
// gas decline curve data
var dataGas = [{
x: summarySiteDate,
y: site_gas,
type: "line",
line: {color: "red"}
}];
var layoutGas = {
title: "Gas BBL",
yaxis: {
type: 'log',
autorange: true
},
xaxis: {
autorange: false,
range: [summarySiteDate[summarySiteDate.length-1], nextYearGraph]
}
};
Plotly.newPlot("gasDeclineCurve", dataGas, layoutGas);
// water decline curve data
var dataWater = [{
x: summarySiteDate,
y: site_water,
type: "line" }
];
var layoutWater = {
title: "Water BBL",
yaxis: {
type: 'log',
autorange: true
},
xaxis: {
autorange: false,
range: [summarySiteDate[summarySiteDate.length-1], nextYearGraph]
}
};
Plotly.newPlot("waterDeclineCurve", dataWater, layoutWater);
})};
I have both HTML sand JS in my code, so it is probably best if you have the whole thing to better understand what I am doing and trying to do. Here are my links:
My repo:
My GitPage
Thank you in advanced!
Here is a function that bases length off of first array length and doesn't care about the number of input arrays
function stackSum(arr) {
return arr[0].map((a, i) => {
let r = 0;
arr.forEach(b, j) => r+=arr[j][i]);
return r;
})
}
console.log(stackSum([
[1,1,1],
[1,2,2],
[2,2,3],
[3,7,7]
]).join(','));
console.log(stackSum([
[1,2,3],
[2,3,4]
]).join(','));
Since the arrays are of equal lengths, you can simply use Array#map.
const
indexSum = (a1, a2) => a1.map((v, i) => v + a2[i]),
desiredArray = indexSum([1, 2, 3], [4, 5, 6])
console.log(desiredArray)

Second Greatest Value Displaying As "undefined"

All variables below are integer values of a range input slider. The code below should log the second greatest value of the sliders to the console. For example, if sliderA = 2, sliderB = 3, and sliderC = 4, the number 3 should be logged to the console. Instead, I'm getting the output "undefined". The code below works when the function is called with integers instead of variables. Does anyone know how to solve this problem?
var AS = Number(getNumber("animalsSlide"));
var MS = Number(getNumber("mathSlide"));
var CS = Number(getNumber("cookingSlide"));
var BS = Number(getNumber("biologySlide"));
var PS = Number(getNumber("performingSlide"));
var WS = Number(getNumber("writingSlide"));
var CAS = Number(getNumber("creativeSlide"));
var IS = Number(getNumber("inventingSlide"));
function secondGreatest(arr_num) {
arr_num.sort(function(x,y) {
return x-y;
});
var uniqa = [arr_num[0]];
var result = [];
for(var j=1; j < arr_num.length; j++) {
if(arr_num[j-1] !== arr_num[j]) {
uniqa.push(arr_num[j]);
}
}
result.push(uniqa[uniqa.length-2]);
return result.join(',');
}
console.log(secondGreatest([AS,MS,CS,BS,PS,WS,CAS,IS]));
You could search for the second large value of the sorted array.
const
values = [5, 5, 5, 4, 4, 3, 2, 1],
second = values.find(((last, count = 0) => value => {
if (last !== value) {
count++;
last = value;
}
return count === 2;
})());
console.log(second);
since it works with integers and not strings, I assume the problem is with the getNumber function.
do you have code there that finds the slider by id?
something like this:
let getNumber = (sliderId) => {
return +document.getElementById(sliderId).value;
}
since the name of the function is getNumber it's easy to assume it returns a number and not a string, so that's what I did here.. and then you won't need to parse to number outside
var AS = getNumber("animalsSlide");
var MS = getNumber("mathSlide");
var CS = getNumber("cookingSlide");
var BS = getNumber("biologySlide");
var PS = getNumber("performingSlide");
var WS = getNumber("writingSlide");
var CAS = getNumber("creativeSlide");
var IS = getNumber("inventingSlide");

construct array back together

My script reads EventSource, and on a message it will get some line data from a variable. That variable will be a array, my script breaks the array down and for each point it flips the x with the y value. It then sends each point as a post request. Is there anyway I can reconstruct the array back together, and then send the post request after flipping each x and y value?
here's my script:
var evtSource = new EventSource("http://URL.com/");
evtSource.onmessage = function(e) {
var obj = JSON.parse(e.data);
var line = JSON.stringify(obj.line)
var line22 = obj.line
//console.log(line22)
line22.forEach(function(point, index){
console.log(JSON.stringify(point)); // console log example// -> "[120,250]"
const [x, y] = point;
console.log(`x: ${x}, y: ${y}`);
var FlipXYvalues = "[[" + y + "," + x + "]]"; // Complies it again... flips the values..
var ident = "String"
if (obj.ident === ident) //the string...
{
$.post("http://URL.com/", {
l: (FlipXYvalues),
w : (obj.lineWidth),
c: (obj.lineColor.replace("#", "")),
o: ("100"),
f: ("1"),
_: ("false")
})
}
});
}
You can use Array#map() to create a new array based on some other array
line22 = [[1,2],[3,4],[5,6],[7,8]];
var newLines = line22.map(point => {
return [point[1], point[0]];
});
//using array destructuring
//if you dont want to mess with specifying indexes
var newLines = line22.map(([x,y]) => {
return [y,x];
});
console.log(JSON.stringify(newLines));
//$.post("http://URL.com/", {l:newLines});

How to convert a big 1D array into series of small key:pair objects in JS?

I am trying to make free hand drawing sketchpad in threeJS. Most of the part has been done and able to draw the shapes. Now my next step is to use the simplify-JS for simplifying the points in my polygon.
The concern is that I am passing my array as buffer geometry and using Line Mesh to draw on screen. I am creating array like [x,y,z,x,y,z,x,y,z... and so on] till MAX_POINTS which is 10000 in my case. Example
Vertices[0.555,0.323,298,0.585,0.353,298,0.615,0.373,298...continuous].
now how can I convert this vertices to
Vertices[{x:0.555,y:0.323,z:298}, {x:0.585,y:0.353,z:298},{x:0.615,y:0.373,z:298}]
..and so on till Vertices.length.
Thanks in advance.
You can use a simple for loop to traverse the array and push objects in a new array.
var vertices = [0.555,0.323,298,0.585,0.353,298,0.615,0.373,298];
var convertedVertices = [];
for(let i = 0; i < vertices.length; i += 3){
convertedVertices.push({ x: vertices[i], y: vertices[i+1], z: vertices[i+2] })
}
console.log(convertedVertices);
UPDATED:
To convert the array back the to the 1D Vertices.
let revert = convertedVertices.reduce(((acc, obj) => acc.concat(obj.x, obj.y, obj.z)), []);
JSBin: https://jsbin.com/sohakufope/edit?js,console
JSBin: https://jsbin.com/bodazugeco/1/edit?js,console
var nverts = [];
for(var i=0;i<vertices.length;i+=3)nverts.push(new THREE.Vector3(vertices[i],vertices[i+1],vertices[i+2]);
hope it helps
var vertices = [
0.555,0.323,298,
0.585,0.353,297,
0.615,0.373,296,
0.123,0.456,295,
0.789,0.012,294,
0.234,0.569,293];
if (vertices.length % 3 === 0) {
var result = [];
for(var i=0; i < vertices.length; i+=3) {
result.push({ x: vertices[i], y: vertices[i+1], z: vertices[i+2] });
}
console.table(result);
}
Would have been nice if you had test data for this but I think something similar to following should work fine.
const keysMap = ['x', 'y', 'z']
let transformedArr = [];
let vertices = [0.555,0.323,298,0.585,0.353,298,0.615,0.373,298...continuous]
while (vertices.length) {
vector = {};
vertices.slice(o, 2).forEach( (val, ind) => {vector[keyMap[ind]] = val} );
transformedArr.push(vector);
vertices = vertices.slice(3);
}

Javascript Containing objects in Arrays

So I've got this code for creating a dialog box in photoshop which contains 4 panels. It worked fine when I wasn't running it through a loop, but the code was bulky and ugly. now I get an error saying "panel1 is undefined". Can I not put objects in arrays like this?
// create a dialog window, dig.panel = dialog panel
var dig = new Window('dialog', 'Poster Interface - Choose 4 Images', [550, 120, 1150, 800]);
//define variables for panel dimensions
var digX = 100;
var digY = 50;
var digWidth = 510;
var digHeight = 140;
var digUp = 110;
var panels = [
[panel1, panel2, panel3, panel4]
];
var labels = [
[label1, label2, label3, label4]
];
var texts = [
[t1, t2, t3, t4]
];
var buttons = [
[bt1, bt2, bt3, bt4]
];
//create panels for the image components
//first loop for panel dimensions multiply by x
// nested loop for contents using i
for (x = 0; x < 4; x++) {
dig.panels[x] = dig.add('panel', [digX, digY + (digUp * x), digWidth, digHeight + (digUp * x)], 'Image ' + (x + 1) + ':');
for (i = 0; i < 4; i++) {
dig.panels[i].labels[i] = dig.panels[i].add('statictext', [20, 20, 120, 40], 'Choose Image' + (i + 1) + ':');
dig.panels[i].texts[i] = dig.panels[i].add('edittext', [125, 20, 325, 40], 'image' + (i + 1) + '.jpg');
dig.panels[i].buttons[i] = dig.panels[i].add('button', [330, 20, 380, 40], 'Open');
}
}
This is wrecking my head. Any advice would be appreciated
To create an array of variables in javascript:
var var1 = "value";
var var2 = "value";
var array = [var1,var2];
You are creating an array, with an array inside it, with variables inside the inner array.
var panels = [
[panel1, panel2, panel3, panel4]
];
You have not displayed to us that those variables have been defined.
Here is a fiddle:
https://jsfiddle.net/chrislewispac/rfhq01L3/
with the following code:
var var1 = "value";
var var2 = "value";
var array = [var1,var2];
var array2 = [[var1,var2]];
console.log(array);
console.log(array2);
Go there and look at the console. You will notice the length of array2 is only 1 while the length of array is 2. So, you need to define your variables, then build the array in the format you want it (do you really want an array of arrays?) Then you can loop and add properties.
Also, an object means something in javascript. In javascript it is typical to see an object created using object literal syntax.
Object = {
property1 : "property",
property2: "property"
}
So when you asked if you could have an array of object, yes you can. However you have to create those objects and their properties first.

Categories