How to move the mouse with webshot function inside a dygraph widget - javascript

I'm stuck because I can not achive what I want with a dygraph widget created in R.
I have this time series.
#library(xts)
dts <- xts::xts(c(10508,8465,3175,3816,4532,2735,2534,9541,8253,9962),
order.by = seq(from = as.Date("2022-02-01"), to = as.Date("2022-11-01"), by = "month"))
I can plot it in a dygraph widget
#library(dygraph)
object <- dygraph((dts),
ylab = "") %>%
dyRangeSelector(height = 20, strokeColor = "") %>%
dyOptions(fillGraph = FALSE, stackedGraph = FALSE, drawGrid = FALSE, strokeWidth = 3) %>%
dyHighlight(highlightSeriesOpts = list(strokeWidth = 3,highlightCircleSize = 10))
and save it in a html page.
#library(htmlwidgets)
saveWidget(object, "temp.html", selfcontained = FALSE)
Now, I would like to take a screenshot of the plot. I know I can do it with
webshot::webshot(url = "images/temp.html", file = "screenshot.png")
No problem with that. But I should hover the mouse near the last data point before the screenshot, because I would like the chart to show the information of the last point in the legend.
I have tried with
webshot::webshot(url = "images/temp.html", file = "screenshot.png"),
eval = "this.mouse.click(1781, 109);")
I got the X,Y coordinates injecting the following code in the inspection frame of the page.
<html onclick="display(event)">
function display(event) {
let X = event.clientX;
let X = event.clientX;
alert = (X + Y);
}
But it does not seem to work.
I get
but I would like something like this
Actually, I would like to hover the last point regardless the time series.
I know it could be done with ggplot and save me a lot of steps but the dygraph is mandatory and I would like to get the static plot from it.
I know also that it is maybe more of a javascript or casperjs question. If so I wouldn't mind repeat the question in the appropriate forum.
In advance, thank you very much for your help.

Related

How to retrieve data from .xls file and use them in three.js 3D model animation?

I developed a dynamic 3D cube with three.js that rotate and translate to visualize data coming from accelerometer and gyroscope.
I initially made the 3D cube rotate and translate using a randomly generate data.
Click on the JSFiddle to see the example I created.
I have two problem which I am struggling to solve and I tried multiple ways to solve but it never worked as intended:
Problem1:
I want to parse data from .xls which contains sample data from accelerometer and gyroscope and feed to the 3D cube so I can move according the value given.
Basically replacing the code below
objects.forEach((obj) => {
obj.rotation.x = speed+1;
obj.rotation.y = speed+2;
obj.rotation.z = speed+34;
obj.position.x = speed+1;
obj.position.y = speed+2;
obj.position.z = speed+2;
});
with a function that parse and iterate through the actual acc and gyro data from the .xls data. Something like that:
objects.forEach((obj) => {
obj.rotation.x = Gyro_X;
obj.rotation.y = Gyro_Y;
obj.rotation.z = Gyro_Z;
obj.position.x = Accelero_X;
obj.position.y = Accelero_Y;
obj.position.z = Accelero_Z;
});
I tried to convert the .xls to .json but the process needs to parse all the data in the file and then I can use it in the 3D cube. This is not feasable because I need to wait a while until all the file is fetched before feeding into the 3D model. I want to be able to loop through the value sequentially to see the 3D cube rotate and translate accordingly.
The data structure are shown below:
this is how I parsed the data from the .xls file using read-excel-file
<script>
var input = document.getElementById('input')
input.addEventListener('change', function() {
readXlsxFile(input.files[0], { dateFormat: 'MM/DD/YY' }).then(function(data) {
// `data` is an array of rows
// each row being an array of cells.
document.getElementById('result').innerText = JSON.stringify(data, null, 2)
}, function (error) {
console.error(error)
alert("Error while parsing Excel file. See console output for the error stack trace.")
})
})
</script>
Problem2:
I want to visualize the accelerometer and gyroscope on the canvas like shown below:
I got answer to my problem in a previous thread I posted. but when I tried to integrated the solution into my code like shown below, it didn't work as it didn't show any information on the canvas as expected. I think I am not placing the .innerHtml in the right position or possibly I missed some other curial steps.
objects.forEach((obj) => {
obj.rotation.x = speed+1;
obj.rotation.y = speed+2;
obj.rotation.z = speed+34;
obj.position.x = speed+1;
obj.position.y = speed+2;
obj.position.z = speed+2;
console.log(obj.position.y);
});
accelPanel.innerHtml = "Accelerometer:<br>X: " + obj.rotation.x + "<br>Y: " + obj.rotation.y + "<br>Z: " + obj.rotation.z;
I would appreciate if there is a solution to my problems, even a hint would help in getting through these issues. That would be much appreciated as I was stuck for a while on these problems.

HighChart with more than 50 data points breaks

I am trying to create a bubble chart using the JS HighChart in Angular2+. Whenever there are more than 50 data points (bubbles), the graph breaks. There are the correct number of bubbles in the correct positions (x,y plots) with all different colors but the sizes are all the same even though the z-values are all different. (I am outputing the z-values in a tooltip and the z-values are accurate)
This function is how I am passing in data to the high-chart configuration.
setSeries() {
this.objData = []
this.Data.forEach(element => {
var x= element['xVal'];
var y = element['yVal'];
var z = element['zVal'].toFixed(0);
var name = element['seriesName'].trim();
var newData =[{
x:x,
y:y,
z:+z,
}]
// SetSeriesData is how i am creating the obj to pass into series=[] in highchart configuration
if(i<50) //If I comment this condition, the graph breaks. Right now, the graph is working properly
this.setSeriesData(sumData, name, this.objData)
i++
})
this.options.series = this.objData;
this.generateChart();
}
This is my setSeriesData function.
setSeriesData(graphData: any, dataName: any, objData: any){
var obj = {};
obj['name'] = dataName;
obj['data'] = graphData;
obj['events'] = {click: function(e) {
//takes me to another link
}};
objData.push(obj)
}
In the above function, I configured the chart so that when you click the bubble, it takes you to another page. When the data points >50, this click functionality is not working either. In addition, the fillOpacity is not correct.
Just a few things to point out
1. I am using Angular 2+
2. The discovered issues are, fillOpacity, click, and size based on z-value.
3. It works perfectly when the data points are less than 50
How can I fix this?

Bokeh: CustomJS TextInput callback to adjust x axis range

I'm trying to make a web page that has a plot powered by an AjaxDataSource object. However, I'd like to have a TextInput widget that can be used to changed the xrange of this plot. Below is a snippet:
source = AjaxDataSource(data={"time": [], "temperature": [], "id": []},
data_url='http://localhost:6543/AJAXdata',
polling_interval=100,
mode='append')
livePlot = figure(x_axis_type="datetime",
x_range=[startDt, endDt],
y_range=(0,25),
y_axis_label='Temperature (Celsius)',
title="Sea Surface Temperature at 43.18, -70.43",
plot_width=800)
livePlot.line("time", "temperature", source=source)
....
updateStartJS = CustomJS(args=dict(xrange=livePlot.x_range), code="""
var startStr = cb_obj.value
alert(startStr)
var newStartMilliSeconds = Date.parse(startStr)
alert(newStartMilliSeconds)
alert(xrange)
alert(xrange.start)
xrange.start = newStartMilliSeconds
alert(xrange.start)
xrange.change.emit();
""")
startInput = TextInput(value=startDt.strftime(dateFmt), callback=updateStartJS)
See this file and the bokeh_ajax() function for the complete implementation: https://github.com/hhprogram/PyramidSite/blob/master/webgraphing/views/ajaxView.py
When I run it and change the corresponding 'Start' textInput box. The CustomJS runs accordingly and per the alerts I have seen that it is capturing the correct new data (assuming you put in an ISO Formatted date like YYYY-mm-dd) but it fails to update the plot axis range (ie the plot doesn't change at all). How would I implement this? (I want to maintain the plots to have underlying AjaxDataSources as well and not use bokeh server - I already know how to implement this type of axis change functionality if running a bokeh server.)
For anyone curious, found my issue. Think the main problem was I was not putting the widget which I wanted to use to control the plot xrange and the actual plot itself within the same layout object. Therefore, when I was calling components on the plot object it didn't include the widget. Then when I included the widget with the plot it worked. See below updates and the updated github repo:
(credit to this post for significantly helping me: Flask + Bokeh AjaxDataSource)
complete file: https://github.com/hhprogram/PyramidSite/blob/master/webgraphing/views/ajaxView.py)
code snippet:
source = AjaxDataSource(data={"time": [], "temperature": [], "id": []},
data_url='http://localhost:6543/AJAXdata',
polling_interval=100,
mode='append')
livePlot = figure(x_axis_type="datetime",
x_range=[startDt, endDt],
y_range=(0,25),
y_axis_label='Temperature (Celsius)',
title="Sea Surface Temperature at 43.18, -70.43",
plot_width=800)
livePlot.line("time", "temperature", source=source)
jsResources = INLINE.render_js()
cssResources = INLINE.render_css()
updateStartJS = CustomJS(args=dict(plotRange=livePlot.x_range), code="""
var newStart = Date.parse(cb_obj.value)
plotRange.start = newStart
plotRange.change.emit()
""")
updateEndJS = CustomJS(args=dict(plotRange=livePlot.x_range), code="""
var newEnd = Date.parse(cb_obj.value)
plotRange.end = newEnd
plotRange.change.emit()
""")
startInput = TextInput(value=startDt.strftime(dateFmt), title="Enter Date in format: YYYY-mm-dd")
startInput.js_on_change('value', updateStartJS)
endInput = TextInput(value=endDt.strftime(dateFmt), title="Enter Date in format: YYYY-mm-dd")
endInput.js_on_change('value', updateEndJS)
textWidgets = row(startInput, endInput)
# NOTE: this is important. Need to have the widgets and plot within same object that is the argument for components() method
layout = column(textWidgets, livePlot)
script, div = components(layout)

ChartJS onclick fill area (Radar Chart)

I'm trying to create a function in JQuery that increase and decrease the points clicked. I've managed to create the onclick event that increases the points, but can't figure out how to decrease it.
$("#radarChart").click(
function (evt) {
var activePoints = myRadarChart.getPointsAtEvent(evt);
var Values = activePoints[0].label + ' = ' + activePoints[0].value;
activePoints[0].value++;
}
);
The above function increases the value of a point when clicked, in order to decrease it I need to know if the filled area is clicked.
I looked at ChartJS documentation and didn't come across it. Other charts do have it, for example Polar Area Chart, when you are hovering a section, it highlights, meaning there is a function that detects mouse hovering over segments.
Does a similar function exist in Radar Charts?
My codepen.
If not, any ideas on how I could achieve this, would be appreciated.
The only alternative I can think of, would be to create 10 buttons, 5 labels. The buttons would be + and -, increase and decrease the label. This takes too much space in my opinion, so I'm trying to avoid it.
Thanks,
There really is no specific chart.js API to do what you are wanting, but you can achieve the same results using the canvas API and a little geometry.
Basically, you want to increase the value if the user clicks outside the current value's region, and you want to decrease if the user clicks inside the current value's region.
I've modified your click handler to do just that.
function getElementPosition(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
};
function getEventLocation(element,event){
// Relies on the getElementPosition function.
var pos = getElementPosition(element);
return {
x: (event.pageX - pos.x),
y: (event.pageY - pos.y)
};
};
function pointDistance(point1, point2) {
return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2));
};
//Get the context of the Radar Chart canvas element we want to select
var ctx = document.getElementById("radarChart").getContext("2d");
// Create the Radar Chart
var myRadarChart = new Chart(ctx).Radar(radarData, radarOptions);
$("#radarChart").click(function (evt) {
var eventLocation = getEventLocation(this,evt);
var activePoints = myRadarChart.getPointsAtEvent(evt);
var eventLocDistToCenter = pointDistance({x: myRadarChart.scale.xCenter, y: myRadarChart.scale.yCenter}, eventLocation);
var activePointDistToCenter = pointDistance({x: myRadarChart.scale.xCenter, y: myRadarChart.scale.yCenter}, activePoints[0]);
if (eventLocDistToCenter < activePointDistToCenter) {
activePoints[0].value--;
} else {
activePoints[0].value++;
}
myRadarChart.update();
});
Note, I also added a call to .update() so that the chart renders the change immediately. With the way you had it implemented, you would not see the chart change until the next render (i.e. when the mouse moves).
Here is a codepen forked from yours with the working solution. Click around to check it out.
Lastly, you probably want to think about upgrading to chart.js 2.0 (latest release is 2.5). 1.0 is long since unsupported and the latest version has LOTS of improvements. You should be able to easily port this over. Post a new question if you need help.

NVD3.js Given X-Axis Value, Get all Lines' Y-Axis Values

I'm using the line-with-focus chart ( View Finder ) example in nvd3. That means there's 3 or 4 lines ( series ) being drawn on the graph. When i hover over any of the lines I want to get back all the y-values for all lines of that given x-axis position ( for the most part these will be interpolated y-values per line ).
I see in the nv.models.lineWithFocusChart source code that using a callback for the elementMouseover.tooltip event I can get my data's x-value back for the data points on the line.
The closest part of the source code that does what i want is with the interactiveGuideline code for the lineChart examples. However, i don't want to create a <rect> overlay with elementMousemove interaction. I think i can modify this code to filter my data and get each line's y-value, but I'm sure there's an easier way I'm not seeing.
I think I'm on the right track, but just wondering if someone had this need before and found a quicker route than the rabbit hole I'm about jump in.
Thanks for feedback
This is the basic functionality you're looking for, it still needs a bit of finesse and styling of the tooltips. (Right now the tooltip blocks the view of the points...)
Key code to call after the drawing the chart in (for example, within the nv.addGraph function on the NVD3 live code site):
d3.selectAll("g.nv-focus g.nv-point-paths")
.on("mouseover.mine", function(dataset){
//console.log("Data: ", dataset);
var singlePoint, pointIndex, pointXLocation, allData = [];
var lines = chart.lines;
var xScale = chart.xAxis.scale();
var yScale = chart.yAxis.scale();
var mouseCoords = d3.mouse(this);
var pointXValue = xScale.invert(mouseCoords[0]);
dataset
.filter(function(series, i) {
series.seriesIndex = i;
return !series.disabled;
})
.forEach(function(series,i) {
pointIndex = nv.interactiveBisect(series.values, pointXValue, lines.x());
lines.highlightPoint(i, pointIndex, true);
var point = series.values[pointIndex];
if (typeof point === 'undefined') return;
if (typeof singlePoint === 'undefined') singlePoint = point;
if (typeof pointXLocation === 'undefined')
pointXLocation = xScale(lines.x()(point,pointIndex));
allData.push({
key: series.key,
value: lines.y()(point, pointIndex),
color: lines.color()(series,series.seriesIndex)
});
});
/*
Returns the index in the array "values" that is closest to searchVal.
Only returns an index if searchVal is within some "threshold".
Otherwise, returns null.
*/
nv.nearestValueIndex = function (values, searchVal, threshold) {
"use strict";
var yDistMax = Infinity, indexToHighlight = null;
values.forEach(function(d,i) {
var delta = Math.abs(searchVal - d);
if ( delta <= yDistMax && delta < threshold) {
yDistMax = delta;
indexToHighlight = i;
}
});
return indexToHighlight;
};
//Determine which line the mouse is closest to.
if (allData.length > 2) {
var yValue = yScale.invert( mouseCoords[1] );
var domainExtent = Math.abs(yScale.domain()[0] - yScale.domain()[1]);
var threshold = 0.03 * domainExtent;
var indexToHighlight = nv.nearestValueIndex(
allData.map(function(d){ return d.value}), yValue, threshold
);
if (indexToHighlight !== null)
allData[indexToHighlight].highlight = true;
//set a flag you can use when styling the tooltip
}
//console.log("Points for all series", allData);
var xValue = chart.xAxis.tickFormat()( lines.x()(singlePoint,pointIndex) );
d3.select("div.nvtooltip:last-of-type")
.html(
"Point: " + xValue + "<br/>" +
allData.map(function(point){
return "<span style='color:" + point.color +
(point.highlight? ";font-weight:bold" : "") + "'>" +
point.key + ": " +
chart.yAxis.tickFormat()(point.value) +
"</span>";
}).join("<br/><hr/>")
);
}).on("mouseout.mine", function(d,i){
//select all the visible circles and remove the hover class
d3.selectAll("g.nv-focus circle.hover").classed("hover", false);
});
The first thing to figure out was which objects should I bind the events to? The logical choice was the Voronoi path elements, but even when I namespaced the event names to avoid conflict the internal event handlers nothing was triggering my event handling function. It seems that a parent <g> event captures the mouse events before they can reach the individual <path> elements. However, it works just fine if instead I bind the events to the <g> element that contains the Voronoi paths, and it has the added benefit of giving me direct access to the entire dataset as the data object passed to my function. That means that even if the data is later updated, the function is still using the active data.
The rest of the code is based on the Interactive Guideline code for the NVD3 line graphs, but I had to make a couple important changes:
Their code is inside the closure of the chart function and can access private variables, I can't. Also the context+focus graph has slightly different names/functionality for accessing chart components, because it is made up of two charts. Because of that:
chart in the internal code is chart.lines externally,
xScale and yScale have to be accessed from the chart axes,
the color scale and the x and y accessor functions are accessible within lines,
I have to select the tooltip instead of having it in a variable
Their function is called with custom event as the e parameter that has already had the mouse coordinates calculated, I have to calculate them myself.
One of their calculations uses a function (nv.nearestValueIndex) which is only initialized if you create an interactive layer, so I had to copy that function definition into mine.
I think that about covers it. If there's anything else you can't follow, leave a comment.

Categories

Resources