SAPui5 sap.viz.ui5.StackedColumn custom tooltip on data point - javascript

I am trying to implement a custom tooltip in a sap viz stacked column chart. This is the small popup window that appears when the mouse hovers a datapoint. My problem is how to detect the position of the data point so that the tooltip popup points to it. Currently it always points to the graph itself, not the datapoint inside the graph.
Here's what I have:
graph = new sap.viz.ui5.StackedColumn({
width: graphWidth,
height : graphHeight,
//.. more properties here
interaction: new sap.viz.ui5.types.controller.Interaction({
decorations: [{name: "showDetail", fn: showDetailHandler}, {name: "hideDetail", fn:hideDetailHandler}]
}),
and the showDetailHandler is currently like this. It is supposed to display a popup pointing to the data point.
var showDetailHandler = function(oEvent){
var data = oEvent.data;
var offset = '0 0';
var tp3 = new sap.ui.ux3.ToolPopup({
content : [ new sap.ui.commons.Button({
text : "button"
}) ],
});
offset = '0 0';
offset = oEvent.position.x + " " + oEvent.position.y;
tp3.setPosition( sap.ui.core.Popup.Dock.CenterBottom,
sap.ui.core.Popup.Dock.CenterTop,
$(oEvent.container),
offset,
"none");
if (tp3.isOpen()) {
tp3.close();
} else {
tp3.open(sap.ui.core.Popup.Dock.CenterBottom, sap.ui.core.Popup.Dock.CenterTop);
}
};
The docs around this are here:
https://sapui5.hana.ondemand.com/sdk/#docs/api/symbols/sap.ui.core.Popup.html#setPosition
https://sapui5.hana.ondemand.com/sdk/docs/api/symbols/sap.viz.ui5.types.controller.Interaction.html#getDecorations
My understanding is that the offset is an offset to the point calculated by the anchor point. Problem is, offset is completely ignored. Popup always appears at the top of the graph.
However, the normal tooltip appears in the correct place.
Any idea/example?
Thank you in advance.

Related

Data label not rendered on sapui5 viz dual y-axis graph when line and column intersect

I have a requirement to show data labels of two graphs on the same axes.
Only when they intersect, one of the two labels won't show. This can be demonstrated below:
As you can see on the 2nd, 5th and 6th columns from the left with values 0%, 7% and 8% respectively
only the orange line values are shown but the blue column values are missing.
This is the final html of the graph after rendering:
So data-datapoint-id 142, 145 and 146 are missing from the html.
I tried using the plotArea.dataLabel.renderer function as a manipulation of what was proposed here but nothing changed, still not rendering.
Anyone encountered a similar problem? Is that a sapui5 issue or can it be fixed by manually inserting the labels into the HTML if so how?
Thanks,
Ori
Using SVG text and jQuery I managed to manually insert the labels into the top middle of the blue rectangle columns.
This is the result, not perfect but works:
and this is the code:
chart.setVizProperties({
plotArea: {
dataLabel: {
formatString: {'פחת כללי': FIORI_PERCENTAGE_FORMAT_2},
renderer: function (oLabel) {
// Create empty text node to be returned by the function such that the label won't be rendered automatically
var node = document.createElement("text");
if (oLabel.ctx.measureNames === "כמות פחת כללי") {
var kamutLabelIdx = oLabel.ctx._context_row_number;
// Use jQuery and SVG to manipulate the HTML
var kamutLabelQuery = '[data-id=\"' + kamutLabelIdx + '\"]';
var kamutColumn = $(kamutLabelQuery)[0];
// Create text element as child of the column
kamutColumn.innerHTML += "<text>" + oLabel.text + "</text>";
var labelNode = $(kamutLabelQuery += ' text');
// Set the label position to be at the middle of the column
const labelLength = 60;
const xPos = (labelLength + oLabel.dataPointWidth) / 2;
const yPos = oLabel.styles['font-size'];
labelNode.attr({
"textLength" : labelLength,
"x" : xPos,
"y" : yPos,
"font-size" : yPos
});
return node;
}
}
}
}
});
The oLabel parameter of the renderer function provides useful info about the data label to be created:
I still wonder if that's a bug with sapui5 vizframe and if there is a simpler way to do this.
Please let me know of your thoughts

How can I align Chart title with the bar dynamically?

How can I align Chart title with the bar in highcharts?
The bar length may vary because of the data, so I want my title to end where the bar is ending.
I want to implement something like this:
To adjust the title you can set tittle.attr in the events.redraw event. You need to remember that a bar chart is an inverted column chart and where you have an x-axis there will be a y-axis.
chart: {
events: {
render: function() {
let chart = this,
series = chart.series[0],
title = this.title,
point = series.points[0];
title.attr({
x: chart.plotTop + point.x,
y: chart.plotLeft + point.y
});
}
}
},
API References:
https://api.highcharts.com/highcharts/chart.events.render
https://api.highcharts.com/class-reference/Highcharts.SVGElement
Demo: https://jsfiddle.net/BlackLabel/ud45prLg/
I would suggest adding a padding-right: ##px as you havent provided any code to work with. try applying the method via inspect element and add the code into your css after with the right amount of "px" needed.

'Click anywhere' event in plotly

I am trying to implement a 'click anywhere' feature in plotly so that I can get the coordinates on user clicks anywhere on plotly charts. The current "official" plotly functionality only works when users click on a plotted data point, but I want to register clicks e.g. on the background white canvas.
Shiny click events for plots can do that, but surprisingly this doesn't seem to exist yet in plotly.
I made some research and found the following codepen implementation which comes close: https://codepen.io/tim-logan/pen/yLXgpyp
#JS
var d3 = Plotly.d3;
var gd = document.getElementById('graph');
Plotly.plot('graph', [{
y: [2, 1, 2]
}])
.then(attach);
function attach() {
var xaxis = gd._fullLayout.xaxis;
var yaxis = gd._fullLayout.yaxis;
var l = gd._fullLayout.margin.l;
var t = gd._fullLayout.margin.t;
gd.addEventListener('mousemove', function(evt) {
var xInDataCoord = xaxis.p2c(evt.x - l);
var yInDataCoord = yaxis.p2c(evt.y - t);
console.log(evt.x, l)
Plotly.relayout(gd, 'title', ['x: ' + xInDataCoord, 'y : ' + yInDataCoord].join('<br>'));
});
}
# HTML
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div style="padding-left: 100px; margin-left: 100px">
<div id="graph"></div>
</div>
</body>
However, as the creator of the codepen pointed out, the click coordinates are not precises as they don't take into account the padding / margin:
One issue with the example workaround is that it doesn't account for padding/margin in parent objects.
I've taken the example and added a parent div that has both padding and margin on the left side, this then skews the value of the x-axis value, and is clearly something that would need to be handled in such a solution as is suggested here.
According to the flot documentation this should be possible for plot objects by subtracting the plot offset from the coordinates:
https://github.com/flot/flot/blob/master/API.md#plot-methods
See e.g. the following excerpt:
Various things are stuffed inside an axis object, e.g. you could use getAxes().xaxis.ticks to find out what the ticks are for the xaxis. Two other useful attributes are p2c and c2p, functions for transforming from data point space to the canvas plot space and back. Both returns values that are offset with the plot offset.
or:
offset()
Returns the offset of the plotting area inside the grid relative to the document, useful for instance for calculating mouse positions (event.pageX/Y minus this offset is the pixel position inside the plot).
I tried to implement a workaround based on offset(), but since my js knownledge is not too good I couldn't get a working version of the code.
Would somebody be able to provide a workaround to fix the offset problem?
I managed to remove the offset by getting the parent box's dimensions. See following example which fixed the above codepen:
var d3 = Plotly.d3;
var gd = document.getElementById('graph');
Plotly.plot('graph', [{
y: [2, 1, 2]
}])
.then(attach);
function attach() {
gd.addEventListener('mousemove', function(evt) {
var bb = evt.target.getBoundingClientRect();
var x = gd._fullLayout.xaxis.p2d(evt.clientX - bb.left);
var y = gd._fullLayout.yaxis.p2d(evt.clientY - bb.top);
Plotly.relayout(gd, 'title', ['x: ' + x, 'y : ' + y].join('<br>'));
});
}
Fixed codepen here: https://codepen.io/flaviofusero/pen/BaZRgzO
Adapted from sleighsoft's implementation here: plotly click events from anywhere on the plot

How do I make Cesium points rotate with the map?

I'm building a cesium app and I need my points to indicate a rotational heading. I'm using billboards to display an image I created to show this heading; however, when I rotate the globe, the points don't rotate with it, making the heading indicators incorrect.
In other words, the rotation of my billboards stay constant with the screen space, but not with the globe.
Here's an aerial view of New York. The points in question are in the lower left corner of the image. Note that the heading indicators are pointing northeast.
Here's the same view, but rotated 180° so that up is South. Now, the heading indicators are still pointing to the northwest of the screen, but I need it to be pointing to the globe's northeast, i.e. towards Manhattan.
Here's the code that displays a point:
var entity = {
id: data.cluster_number + '_' + point.id,
name: point.id,
position: Cesium.Cartesian3.fromDegrees(point.lon, point.lat),
billboard: {
image: 'assets/img/point.png',
color: color,
rotation: -1 * toRadians(point.heading),
scale: point.size * 0.07
}
};
if (!angular.isDefined(viewer.entities.getById(entity.id))) {
viewer.entities.add(entity);
}
What's the easiest/most effective way to get the points to rotate with the globe/map?
You need to do a few things.
1 - Update the rotation property of the billboard to be a CesiumCallbackProperty.
rotation: new Cesium.CallbackProperty(
function () {
return -1 * toRadians(point.heading);
}, false);
2 - You'll need to update the heading property of the point based on the camera's heading.
point.heading = viewer.camera.heading;
3 - You'll want to update that point on an interval, either using the cesium clock:
mapContext.clock.onTick.addEventListener(function() {...});
or using JavaScripts setInterval
Once all that is set up here is how it works.
The billboard on the map continuosly pulls the rotation from the point, and if it changes, it will update the billboard on the map. Each time the interval or clock ticks, the value being used changes, based on the heading of the camera.
NOTE: The heading property on the camera is in radians.
Set the billboard's alignedAxis to Cesium.Cartesian3.UNIT_Z instead of the default screen up vector. Run following sample in SandCastle to demonstrate that it works:
var viewer = new Cesium.Viewer('cesiumContainer');
var position = Cesium.Cartesian3.fromDegrees(-111.572177, 40.582083);
var cesiumTerrainProviderMeshes = new Cesium.CesiumTerrainProvider(
{ url : '//assets.agi.com/stk-terrain/world' }
);
//viewer.terrainProvider = cesiumTerrainProviderMeshes;
//viewer.scene.globe.depthTestAgainstTerrain = true;
var ellipsoid = viewer.scene.globe.ellipsoid;
var billboardCollection = viewer.scene.primitives.add(new Cesium.BillboardCollection(
{ scene : viewer.scene }
));
billboardCollection.add(
{ position : position, image : '../images/facility.gif', //heightReference : Cesium.HeightReference.CLAMP_TO_GROUND, rotation: Cesium.Math.PI/4, alignedAxis : Cesium.Cartesian3.UNIT_Z }
);

Flot event for range updates in response to panning/zooming

For Flot, is there an event that fires after the user has completed panning or zooming using the mouse scroll wheel (after the range.xaxis.to/from and range.yaxis.to/from have settled)? I am trying to use the line below to update the selection on an overview plot after the user has panned or zoomed in the main plot, but am finding that either the update to the overview plot happens after panning or zooming(not both).
$("#plot").bind("mouseup scroll",updateOverviewSelection);
Edit: http://jsfiddle.net/apandit/nu2rr58h/9/
In the jsfiddle, I am unable to pan in the main plot and the cursor does not seem to change back to normal. The user can click and drag in the overview plot to make a selection, which leads to zooming in the main plot. I would also like to be able to allow the user to pan and zoom in the main plot and have the selection box in the overview plot updated; I am attempting to do this by binding the updateOverviewSelection method to the plot div for the scroll and mouseup events. Is there an event in Flot that fires every time the x- and y-axis limits are updated?
The solution to this issue is below. The issue was that setting the overview plot's selection(overview.setSelection(ranges);) was triggering the zoom method because it was bound to the plotselected event in the overview plot. At the end of the zoom method, the main plot was plotted, which was again calling the overview.setSelection(ranges); line in the updateOverviewSelection method. To prevent this ping-pong between the two methods/events, I added an updatingOverviewSelection flag.
http://jsfiddle.net/apandit/nu2rr58h/12/
var datasets = [[
[0,0],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9]
],
[
[0,0],[-1,-1],[-2,-2],[-3,-3],[-4,-4],[-5,-5],[-6,-6],[-7,-7],[-8,-8],[-9,-9]
]];
var plot = $.plot("#plot",datasets,{
pan: {
interactive: true
},
zoom: {
interactive: true,
mode: "x"
}
});
var overview = $.plot("#overview",datasets,{
selection: {
mode: "xy"
}
});
var updatingOverviewSelection = false;
$("#plot").bind("plotpan plotzoom",updateOverviewSelection);
$("#overview").bind("plotselected", zoom);
function zoom(event,ranges) {
if(updatingOverviewSelection) {
updatingOverviewSelection = false;
}
else {
var options = plot.getOptions();
options.xaxes[0].min = ranges.xaxis.from;
options.xaxes[0].max = ranges.xaxis.to;
options.yaxes[0].min = ranges.yaxis.from;
options.yaxes[0].max = ranges.yaxis.to;
plot = $.plot("#plot",datasets,options);
}
};
// get the window x-axis and y-axis ranges for the main plot
// and set the selection in the overview plot to those ranges
function updateOverviewSelection(event) {
var options = plot.getOptions();
var ranges = {
xaxis: {
from: options.xaxes[0].min,
to: options.xaxes[0].max
},
yaxis: {
from: options.yaxes[0].min,
to: options.yaxes[0].max
}
};
updatingOverviewSelection = true;
overview.setSelection(ranges);
};

Categories