Positioning cdk Overlay at mouse position - angular-material

I try to position the overlay with the top right corner at the mouse position. To visualize it, I will show you a graphic how it should be and what I got.
So to position the overlay with the top right corner at the mouse position, the width of overlay has to be subtracted from the event.clientX value (see below). BUT the total width has to be dynamically, depending on how wide the content in the overlay will be. That could differ over time. So How to get the exact value of the width of the overlay and position it afterwards?
Button to open the Overlay:
<button (click)="showTasks($event)"></button>
Method which is called:
export class Component {
constructor(private _tasksOverlay: OverlayService) {}
showTasks(event) {
this._tasksOverlay.open(OverlayComponent,event);
}
}
OverlayService:
#Injectable()
export class ServerTaskOverlayService {
private _config = new OverlayConfig({});
constructor(private overlay: Overlay) {}
open(comp:ComponentType<any>,event:MouseEvent) {
// Here the width of the overlay should already be known
this._config.positionStrategy = this.overlay.position().global().left(event.clientX+"px").top(event.clientY+"px");
const overlayRef = this.overlay.create(this._config);
const filePreviewPortal = new ComponentPortal(comp);
overlayRef.attach(filePreviewPortal);
}
}

try to create overlay with PositionStrategy and then change left value.
For example:
this.overlayRef = this.overlay.create({
positionStrategy: this.positionStrategy
});
this.overlayRef.attach(new TemplatePortal(this.templateRef, this.viewContainerRef));
this.positionStrategy.left(`${event.clientX - element width}px`);
this.positionStrategy.apply();

After many attempts, I realized that the bottom-left corner is the (0,0) axis-point and the y-axis should have a negative value (don't know why), so the code that worked for me is:
const positionStrategy = this.overlay.position().global().left(event.clientX+"px")
.top((-1 * (window.innerHeight - event.clientY))+"px");
this.overlayRef = this.overlay.create({
positionStrategy
});

Related

How to position highcharts tooltip above chart with outside:true

I have a system with lots of highcharts which can be positioned basically anywhere on the page.
Some are very small (e.g. 50px x 50px).
I see we can set tooltip.outside : true
tooltip: {
outside: true
}
This stops the tooltip taking over the whole chart container by breaking it out of the chart and into the window.
However this can cause overflow issues with the window when you're near the edge of the page and I personally prefer a static tooltip.
I'd like to always fix the tooltip to float above the chart, top left with a bit of padding, which for my application will almost always be visible and will avoid overflow issues, e.g.;
I've looked into setting a custom positioner, however, as the "outside" tooltip is now part of the window and not relative to the chart, the positioner sets a fixed position, which isn't suitable for my application.
I'd like the tooltip to always be above the chart regardless of mouse or scroll positions.
Of course I could add a custom element and position it above the chart myself, then applying the tooltip to that, but we have a lot of charts and this seems cumbersome.
Tooltip outside Fiddle
Suggestions?
Thanks
After a bit of poking around in the highstock Tooltip.prototype.getPosition code, it turns out what I needed was this.point.chart.pointer.getChartPosition();
tooltip: {
distance: 40,
outside: true,
positioner: function () {
var point = this;
var chart = point.chart;
var chartPosition = chart.pointer.getChartPosition();
var distance = point.distance;
//Position relative to renderTo container
return {
x: chartPosition.left - distance,
y: chartPosition.top - distance - (point.options.useHTML == true ? point.label.div.offsetHeight : point.label.height)
}
//Alternatively - Position relative to chart plot (ignoring legend)
var containerScaling = chart.containerScaling;
var scaleX = function (val) {
return (containerScaling ? val * containerScaling.scaleX : val);
};
var scaleY = function (val) {
return (containerScaling ? val * containerScaling.scaleY : val);
};
return {
x: chartPosition.left - distance + scaleX(chart.plotLeft),
y: chartPosition.top - distance + scaleY(chart.plotTop) - point.label.height
}
},
},
See working fiddle.
I find it odd that this method is attached to pointer, but it's what I was after.
One thing to note, in the fiddle I use point.label.height, if useHTML:true; use point.label.div.height.
What about using the positioner callback without setting the 'outside' option? It will set the wanted position inside the chart area.
Demo: https://jsfiddle.net/BlackLabel/gabjwd2e/
API: https://api.highcharts.com/highcharts/tooltip.positioner

How can an effect find out the display size of the UIImageView it is attached to?

In Xamarin.Forms, an Effect can be attached to a View. In my case, the View is displaying an Image. And the effect is making a colored "glow" around the visible pixels of the image. XAML:
<Image Source="{Binding LogoImage}" ...>
<Image.Effects>
<effects:GlowEffect Radius="5" Color="White" />
</Image.Effects>
</Image>
The effect is implemented as a subclass of RoutingEffect:
public class GlowEffect : Xamarin.Forms.RoutingEffect
{
public GlowEffect() : base("Core.ImageGlowEffect")
{
}
...
}
On each platform, there is a PlatformEffect to implement the effect. For iOS:
using ...
[assembly: ExportEffect(typeof(Core.Effects.ImageGlowEffect), "ImageGlowEffect")]
namespace Core.Effects
{
public class ImageGlowEffect : PlatformEffect
{
protected override void OnAttached()
{
ApplyGlow();
}
protected override void OnElementPropertyChanged( PropertyChangedEventArgs e )
{
base.OnElementPropertyChanged( e );
if (e.PropertyName == "Source") {
ApplyGlow();
}
}
private void ApplyGlow()
{
var imageView = Control as UIImageView;
if (imageView.Image == null)
return;
var effect = (GlowEffect)Element.Effects.FirstOrDefault(e => e is GlowEffect);
if (effect != null) {
CGRect outSize = AVFoundation.AVUtilities.WithAspectRatio( new CGRect( new CGPoint(), imageView.Image.Size ), imageView.Frame.Size );
...
}
}
...
}
}
The above code works if Source is changed dynamically: at the time Source changes, the UIImageView (Bounds or Frame) has a size. BUT if the Source is set statically in XAML, that logic does not run: so the only call to ApplyGlow is during OnAttach. UNFORTUNATELY, during OnAttach, the UIImageView has size (0, 0).
How get this effect to work on iOS, with a static Source?
NOTE: The equivalent Android effect works via a handler attached to ImageView.ViewTreeObserver.PreDraw - at which time the size is known. So if there is an iOS equivalent event, that would be one way to solve.
More Details:
The original implementation used the original image size (imageView.Image.Size) - which is available during OnAttach. This can be made to "work", but is not satisfactory: the glow is applied to the full size image. If the image is significantly larger than the view area, the glow becomes much too small a radius (iOS shrinks the image+glow as it renders): it does not have the desired appearance.
ApplyGlow has an option to apply a tint color to the image. That tint color is different than the glow color. I mention this because it restricts the possible solutions: AFAIK, can't just set options on an image and let iOS figure out how to render it - need to explicitly resize the image and draw the resized tinted image on top of a blurred version (the glow). This code all works - if imageView.Bounds.Size (or imageView.Frame.Size) is available (and non-zero).
With a breakpoint in OnElementPropertyChanged, I've checked to see if imageView size is known for any property that is always set. No; if no properties are dynamically set, the property changes all occur before imageView has a size.
Maybe it's a workaround and I don't know if it is acceptable to you.
Add a little delay before calling ApplyGlow(); in OnAttached. After the delay, you will get the imageView.Frame.Size or imageView.Bounds.Size.
protected override async void OnAttached()
{
await Task.Delay(TimeSpan.FromSeconds(0.3));// it can be 0.2s,0.1s, depending on you
ApplyGlow();
}
And if you have set WidthRequest, HeightRequest, you can get there without the delay:
private void ApplyGlow()
{
var imageView = Control as UIImageView;
if (imageView.Image == null)
return;
Console.WriteLine(imageView.Image.Size.Width);
Console.WriteLine(imageView.Image.Size.Height);
CoreGraphics.CGSize rect = imageView.Bounds.Size;
CoreGraphics.CGSize rect2 = imageView.Frame.Size;
Console.WriteLine(rect);
Console.WriteLine(rect2);
double width = (double)Element.GetValue(VisualElement.WidthRequestProperty);
double height = (double)Element.GetValue(VisualElement.HeightRequestProperty);
Console.WriteLine(width);
Console.WriteLine(height);
double width2 = (double)Element.GetValue(VisualElement.WidthProperty);
double height2 = (double)Element.GetValue(VisualElement.HeightProperty);
Console.WriteLine(width2);
Console.WriteLine(height2);
}

highstock crosshair width according to zoom

I have a highstock chart witch candlestick data type.
When I mouseover the data point, I want to highlight the background of the point.
This is what tooltip -> crosshairs do:
tooltip: {
crosshairs: true
}
But the only width option to set is the fixed width. See http://jsfiddle.net/8YBd7/.
This fixed width works with initial zoom, but when I change the zoom, the width is not updated with new point width.
When I set 100% width, the crosshair would fill entire chart area:
tooltip: {
crosshairs: {
width: '100%'
}
}
Is there another option how to highlight current data point by changing its background or setting the width to the pointPixelInterval or something else?
Meanwhile, I produced a dirty workaround, so improvements are welcomed:
xAxis: {
events: {
afterSetExtremes: function() {
this.chart.tooltip.crosshairs = [];
this.chart.options.tooltip.crosshairs.width = (this.width / (this.series[0].points.length-1));
}
}
}
http://jsfiddle.net/QbdEu/1/
Whenever the zoom is changed, the width is recounted according to chart width and number of displayed data points. The width is not updated when calling redraw(), so the old crosshair needs to be removed.
Have you tried to use Renderer http://api.highcharts.com/highstock#Renderer.rect() which allows to plot any shapes and defined width? Only what you need is getting point width in pixels frpm(chart.series[0].data[0].graphic) and then setting correct widht of "shape".

How do I get my highcharts to reduce in size when the window is resized down

I'm having trouble getting my highchart to reduce in size when the window is resized down.
I have created an example of my code in JSFiddle http://jsfiddle.net/britboy/UvFaQ/
<table border='1' width='100%'><tr><td width='100%'>
<div id="container" width='100%'></div>
</td></tr></table>
Its a simple Highchart chart displayed in a table - if I make the window bigger the chart expands, however if I make the window smaller the chart doesn't want to reduce instead the table gets scroll bars.
I've tried setting up a resize event and then adjusting the chart size with chart.setSize( ) but the problem is the div containing the chart never reduces any further in size, so the setSize() does not get triggered. Since the chart resizes automatically when the chart container gets bigger I would have though the same should work when the chart gets smaller. I think the problem is that the chart's size is preventing its container from shrinking.
How do I code a chart in a table that will reduce in size when the table is reduced?
Thanks
There are n number of ways to accomplish it.
One would be as pointed out by #Azeem. in the comment.
Other way, I actually, bind it with window. Whenever the window re-sizes, bind event would trigger a function to re-size the div element used to render the chart.
$(window).bind("resize", resizeChart);
then,
function resizeChart() {
var width = $(document).width() - 55;
var height = $(document).height() - 60;
$("#container").css("width", width);
$("#container").css("height", height);
}
Check out the sample fiddle here.
Try this method :
var chart = $('#graph1').highcharts();
var DetailsWidth = $('#graph1');
http://jsfiddle.net/cjohn/5ghzk4t8/4/
[Adding a solution for react-highcharts here, because this pops up as first SO solution when searching for "react-highcharts shrink" on Google]
For react-highcharts you can simply call the React Component forceUpdate() method in the window resize event listener. It will trigger Highcharts to redraw to the new, smaller area. See also Rerender view on browser resize with React
NOTE: my code was tested using flex-auto (see CSS Flexible Box Layout) in the parent and the Highcharts DOM elements. It would be interesting to know if this works for other layouts too.
import React from 'react';
import Highcharts from 'react-highcharts';
...
export default class Chart extends React.Component {
constructor(props) {
...
// install callbacks
this.resize = () => {
// throttle resize events as redraw is expensive
if (this.timeout)
return;
this.timeout = setTimeout(
() => {
// force Highcharts to always adhere
// to changes in the view area
this.forceUpdate();
this.timeout = undefined;
},
50); // milliseconds
};
...
}
...
componentDidMount() {
...
// listen to window resize events
window.addEventListener('resize', this.resize);
...
}
...
componentWillUnmount() {
...
// remove listener when component goes away
window.removeEventListener('resize', this.resize);
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = undefined;
}
...
}
...
render() {
...
return (
<Highcharts
config={...}
domProps={{
className: 'd-flex flex-auto',
...
}}
...
/>
);
}
...
}

highcharts grouped chart using custom bar width with no pointPadding

Basically i want to use pointPadding with custom bar width (pointWidth) both at the same time. But i can't, here is my problem:
I want a chart with no-padding in the bars of one group when showing grouped chart. For this purpose i used pointPadding: "0" and it shows me exactly right chart. like:
if(chart_json.chart.defaultSeriesType=='column'){
if(chart_json.xAxis.categories.length >= 1 ){
chart_json.plotOptions.column.groupPadding = "0.05";
chart_json.plotOptions.column.pointPadding = "0.00";
}
}
var chart=new Highcharts.Chart(chart_json,startupObj.startupFunction);
chartObjArray.push(chart);
But when i set my custom width it adds some spaces in between bars of one group.
Actually i want to put max and min limit on bar width, but i can't find any support from Highchart library. So for this purpose when highchart generate the chart, if the generated chart bar width exceeds my max-limit i set it to my max-limit and same for lower limit.
if(chart_json.chart.defaultSeriesType=='column'){
if(chart_json.xAxis.categories.length >= 1 ){
chart_json.plotOptions.column.groupPadding = "0.05";
chart_json.plotOptions.column.pointPadding = "0.00";
}
}
var chart=new Highcharts.Chart(chart_json,startupObj.startupFunction);
chartObjArray.push(chart);
//set bar width
if(chart_json.chart.defaultSeriesType=='column' || chart_json.chart.defaultSeriesType=='bar'){
setChartBarMaxMinWidth(chart,chart_json);
}
setChartContainerWidth(chart_json,chart);
////************************************///////////////////
function setChartBarMaxMinWidth(chart,json){
var maximumBarWidth=70;
var minimumBarWidth=15;
chart_json=eval('('+json.chart_json+')');
for(var j=0;j<chart.series.length;j++){
var series=chart.series[j];
if (series.data[0].pointWidth > maximumBarWidth) {
chart.series[j].options.pointWidth = maximumBarWidth;
isMaxMin='MAX';
}
if (series.data[0].pointWidth < minimumBarWidth) {
chart.series[j].options.pointWidth = minimumBarWidth;
isMaxMin='MIN';
}
};
function setChartContainerWidth(chartOptions,chart){
// calculate # of bars
var numberOfBars=seriesGroupLength*categoriesLength;
// calculate container width
// var containerWidth=numberOfBars*43;
// get chart bar width
var containerWidth=numberOfBars*barWidth;
chart.setSize($(container).width(),$(container).height());
};
In setChartContainerWidth() i used highchart setSize() method to resize chart..
Thanks if someone can help me to remove spaces in between bars of one group.
Thanks
I used the spacingTop attribute to enforce a maximum pointWidth:
if (series[0].data[0].pointWidth > maximumBarWidth) {
this.options.chart.spacingTop = this.options.chart.height - (maximumBarWidth * series.length) - 30;
this.isDirtyBox = true;
this.redraw();
}
So if there is one Bar over a certain Width the spacing will be adjusted and the chart is drawn smaller.
This does not change the size of the container but keeps the bars beneath a certain size and your pointPadding and groupPadding will not be affected.
You will have to adjust the calculation of the spacingTop depending on your data structure and the size of your axis titles.

Resources