How to handle fill toggle with mouseover / mouseout events on group - konvajs

In jquery we have toggles for this.
If I am doing it jquery DIY I would stash the 'on' and 'off' fill colors in a 'data' attribute of the element and retrieve each as needed.
What is an efficient method to effect a mouseover fill color toggle in Konva?
Example: Say I have a layer, and on this I have a group containing a rect. A mouseover and mouseout switcharoo to highlight the rect by changing its fill color on mouseover and back to normal on mouseexit might be
rect.on('mouseover', function(evt){
var shape = evt.target;
// Uh-oh, I need to stash the current fill color somewhere
shape.fill('lime');
})
rect.on('mouseexit', function(evt){
var shape = evt.target;
shape.fill('that_stashed_fill_color'); // < how to get the stashed val and from where ?
})
Any thoughts?
EDIT: My own attempt was to use
rect.on('mouseover', function(evt){
var shape = evt.target;
$(shape).data('bgColor', shape.fill()); // stash current in data
shape.fill('lime');
})
rect.on('mouseexit', function(evt){
var shape = evt.target;
shape.fill($(shape).data('bgColor')); // get the stashed val from jq data
})
which works but the use of the jq wrapper feels like an overhead I would prefer to avoid.

You can use almost any custom attribute in Konva nodes (make sure you don't overlap with existing properties, so you don't have an unexpected result). Set - shape.setAttr('anyAttibute', anyValue); get - shape.getAttr('anyAttibute');
You can do this:
rect.on('mouseenter', function(evt){
var shape = evt.target;
shape.setAttr('oldFill', shape.fill());
// set new fill
shape.fill('lime');
shape.getLayer().batchDraw();
})
rect.on('mouseleave', function(evt){
var shape = evt.target;
shape.fill(shape.getAttr('oldFill'));
shape.getLayer().batchDraw();
})
But personally I prefer to use this:
const FILL_COLOR = 'red';
const HIGHLIGHT_COLOR = 'lime';
shape.fill(FILL_COLOR);
shape.on('mouseenter', function(evt){
shape.fill(HIGHLIGHT_COLOR);
})
shape.on('mouseleave', function(evt){
shape.fill(FILL_COLOR);
})

Related

Openlayers 3 popover putting each word on one line

Working on an OL3 map, and am getting stuck on popover style. I haven't added any CSS to change it at all and it is currently being handled by default Bootstrap settings.
Okay so the problem: each word is being displayed on it's own line. How can I change this? Or, a better question, how do I control the style of the popovers?
This is was it looks like now: http://i.imgur.com/lrLYbag.png
The code handling popups:
//popups
var element = document.getElementById('popup');
var popup = new ol.Overlay({
element: element,
positioning: 'bottom-center',
stopEvent: false
});
map.addOverlay(popup);
// display popup on hover
map.on('pointermove', function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel,
function(feature, layer) {
return feature;
});
if (feature) {
var geometry = feature.getGeometry();
var coord = geometry.getCoordinates();
popup.setPosition(coord);
$(element).popover({
'placement': 'top',
'html': true,
'content': feature.get('name')
});
$(element).popover('show');
} else {
$(element).popover('destroy');
}
});
Thanks!
I think you just need to add some css to the #popup element you're using for the popup.
Something like:
#popup {
min-width: 280px;
}
Take a look at this example http://openlayers.org/en/master/examples/popup.html for styling ideas.

Two markers at the same position

2 markers at the same position how not display the tooltip of the hidden marker? When I pass over the visible markers.
map.on('pointermove', function(e) {
var feature = map.forEachFeatureAtPixel(e.pixel, function(feature, layer) {
if (layer == layer_1) {
return feature;
}
});
map.getTarget().style.cursor = feature ? 'pointer' : '';
tooltip.style.display = feature ? '' : 'none';
if (feature){
overlay.setPosition(e.coordinate);
tooltip.innerHTML = 'marker hidden';
}
});
Can't just comment or I would since I only want to ask you: did you try using setStyle() instead to change the style? That usually forces the change event. I'm asking that because I had a similar issue here to hide/show features.

How to find style text that was previosly set on a feature - OL3

Using OL3, I set the text on a style dynamically:
var myLayer = new ol.layer.Vector({
source: mySource,
style: function (feature, resolution) {
var style = new ol.style.Style({
text: new ol.style.Text({
text: setText(feature)
})
});
return [style];
}
});
I am trying to later read what is stored in text:
text: setText(feature)
I am trying to retrieve the text on a click event but not sure how to access that property under the feature style (feature is the variable from the event containing the clicked feature):
// Get current display text
var currentFeatureStyle = feature.getStyle();
But when I do that, I get a null currentFeatureStyle.
Also tried looping through the feature:
for (var fid in feature)
{
//what to look for to extract the feature text?
But not sure what to look for to extract the feature text. Any help getting back the feature text from a feature would be appreciated.
Thanks
If someone can come up with a more correct answer please by all means post below. I have created a "workaround" since I could not retrieve the text from the feature. The workaround involves the following:
Inside the method the text is originally set, add the following:
function setText(feature)
{
// First do your normal stuff (set the text of the feature)
var output = "myFeatureText";
// Here is the workaround step 1:
// Create a property and set it to the text
feature.displayText = output;
// Return back value for the setting of the style text
return output;
}
Now the text is also saved in a property called displayText. Note: displayText is a made up property, it does not exist in openlayers.
Step 2 is to retrieve the property you created in step 1 to get the display text. In this example we are retrieving the feature from singleclick but it can be from anywhere else you are using the feature:
map.on('singleclick', function (evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel,
function (feature, layer) {
return feature;
});
// Here is the workaround step 2:
// Get "display text" custom field for this feature
var displayText = feature.displayText;
.
And thats all there is to it. There must be a "correct" way to retrieve the text from the style of the feature but I am not aware of it. If someone know how to by all means post your answer.

Two bar graphs in the same place, controlled by one slider D3.js

I am attempting to create a bar graph that when independent sliders are moved they change two bar graph svg heights at the same time and they are stacked, they are different colors show it shows two separate values in the same graph, basically showing growth vs the current. I am using jquery-ui and D3.js. Currently it only moves the one svg elements instead of both at the same time, Id like them both to move at the same time.
HTML
<div id="slider" class="slider">
<label for="amount">Age</label>
<input type="text" id="amount1" style="border:0; font-weight:bold;">
</div>
<div id="slider1" class="slider">
<label for="amount2">Retirement Age</label>
<input type="text" id="amount2" style="border:0; font-weight:bold;">
</div>
JS
//initialize sliders
jQuery(document).ready(function($) {
$("#slider").slider({
max: 100
});
$("#slider").slider({
min: 18
});
$("#slider1").slider({
max: 100
});
$("#slider1").slider({
min: 18
});
//slider actions
$("#slider, #slider1").slider({
value: 10,
animate: "fast" ,
slide: function (event, ui) {
//capture the value of the specified slider
var selection = $("#slider").slider("value");
var selection1 = $("#slider1").slider("value");
//fill the input box with the slider value
$( "#amount1" ).val( selection );
$( "#amount2" ).val( selection1 );
//set width and height, actually I'm a little confused what this is for
var w = 200;
var h = 200;
//data arrays for svgs
var dataset = [];
var dataset1 = [];
//fill the data arrays with slider values
dataset.push(selection);
dataset.push(selection1 + selection);
//draw rectangle on the page
var rectangle = svg.selectAll("rect")
.classed("collapse", true)
.data(dataset);
**
THIS IS WHERE IT CONFUSES ME
**
//I draw the second rectangle here, however I choose the same svg element,
//Im not sure what other way to get it to appear in the same space but
//I am sure this is what is causing my issues
var rectangle1 = svg.selectAll("rect")
.classed("collapse", true)
.data(dataset1);
//not sure what this does
rectangle.enter().append("rect");
rectangle1.enter().append("rect");
rectangle.attr("width", 200).transition().attr("fill", "#A02222").attr("height", function (d) { console.log('d is ' + d);
return d;
}).attr("x", function (d) {
return 40; //I dont know why I return 40?
}).attr("y", function (d) {
return 40; //Same here dont know why I return 40?
});
rectangle1.attr("width", 200).transition().attr("height", function (d) { console.log('d is ' + d);
return d;
}).attr("x", function (d) {
return 40; //I dont know why I return 40?
}).attr("y", function (d) {
return 40; //Same here dont know why I return 40?
});
}
// slider actions ends here
});
//Create SVG element
var svg = d3.select(".svgContain").append("svg").attr("width", 125).attr("height", 300);
});
For starters, you may want to follow this tutorial: http://bl.ocks.org/mbostock/3886208
The "return 40;" that you are wondering about are actually what will specify the position and dimensions of the rect's you're appending to the svg. Those shouldn't just be 40, they should be bound to values in the data set, or based on the index of the bar's series in the set of series or something more meaningful than 40.
There is a stacked bar chart data processor that will take a set of series and spit out a new set of series coordinate definitions that make it easier to calculate how rect's will stack in svg coordinate space: https://github.com/mbostock/d3/wiki/Stack-Layout
Then, there's the more general issue of how to deal with these "nested" data sets where you have series, and in the series there are values and you don't want to have to manually track and select individual series. There are several ways to handle this sort of situation. If you know you will only ever have two series, and you really want fine-grained control over each independently, you could assign the top level object an id and then start the data join for each of the plots by selecting that top level object by id... eg:
var container1 = d3.select("#myContainer1);
container1.selectAll("rect").data(myData1).append("rect");
var container2 = d3.select("#myContainer2);
container2.selectAll("rect").data(myData2).append("rect");
If you do something like that, the first select basically sets the context of the subsequent selects. So, only the rects inside of the "#myContainer1" or "#myContainer2" will get selected by each "selectAll" based on which context you're in.
The other approach is to use nested selections. Nested selections are a little more complicated to wrap your head around, but 90% of the time, this is the approach I use. With nested selections, you would restructure your data slightly and then apply nested selects/joins to bind each series to a dom element and then the values of each series to subelements of each of the series dom elements.
First, read this: http://bost.ocks.org/mike/nest/
and then try making your data something more like this:
data = [
{ key: "series1", values: [...]},
{ key: "series2", values: [...]}
];
Then, you will want to do a nested selection where you start with a selection of the "data" array and bind it to whatever svg or html element you have that wraps each of the two series.
var series = d3.select("svg").selectAll("g.series")
.data(data, function(d){return d.key; });
series.enter().append("g").attr("class", "series");
At this point, d3 will have added a "g" element to your svg element for each series and bound the series object (including the key and values array) to the appended elements. Next, you can make a nested selection to add series-specific elements to the g element... ie:
var rect = series.selectAll("rect").data(function(d) { return d.values });
rect.enter().append("rect");
Note that we used a function in our ".data(...)" call. That's because the values we want passed to the join actually depend on which specific series is being processed by D3.
Now, you'd have a rect added to the g element for each value in each series. Since you used d3 to do the data binding and you used the key function in the first select (".data(data, function(d){return d.key;}"), future selects done in the same nested/keyed manner will update the right g and rect elements.
Here's a Fiddle that demonstrates the concept:
http://jsfiddle.net/reblace/bWp8L/2/
A key takeaway is that you can update the data (including adding additional series) and the whole thing will redraw correctly according to the new nested join.

HighCharts Keep Vertical Line on Click Event

Using this example: http://jsfiddle.net/gh/get/jquery/1.7.2/highslide-software/highcharts.com/tree/master/samples/stock/demo/candlestick-and-volume/
When you hover over points on the chart, you get a nice vertical line showing you which point you're currently on. I want to modify the click event so that the vertical line stays when I hover away after a click. Changing the line color would be ideal on click, but not necessary.
If I click another point I'd want to remove any previous lines. Any ideas on how I could accomplish this?
The above solution like I said, is really cool, but is kind of a hack (getting the path of the crosshair) into the implementation details of highcharts, and may stop working in future releases, may not be totally cross browser (esp since <IE8 do not support SVG, the adding path may still work as it should be handled by highchart's add path method, but getting the crosshair's path may not work, I may be wrong, am an SVG noob). So here I give you the alternate solution of dynamically adding plotLines. PlotLines also allow some additional features like dashStyles, label etc.
get the axis and x value of point clicked (may not exactly overlap the crosshair)
var xValue = evt.xAxis[0].value;
var xAxis = evt.xAxis[0].axis;
Or
EDIT If you want to have the plotLine at the location of the crosshair and not the click position, you can use following formula (No direct API to get this, obtained from source code hence may stop working if code changes)
var chart = this;
var index = chart.inverted ? chart.plotHeight + chart.plotTop - evt.chartY : evt.chartX - chart.plotLeft;
var xValue = chart.series[0].tooltipPoints[index].x;
Add plotline
xAxis.addPlotLine({
value: xValue,
width: 1,
color: 'red',
//dashStyle: 'dash',
id: myPlotLineId
});
You can cleanup existing plotline
$.each(xAxis.plotLinesAndBands,function(){
if(this.id===myPlotLineId)
{
this.destroy();
}
});
OR
try {
xAxis.removePlotLine(myPlotLineId);
} catch (err) {}
Putting the pieces together
var myPlotLineId="myPlotLine";
...
var chart=this;
index = chart.inverted ? chart.plotHeight + chart.plotTop - evt.chartY : evt.chartX - chart.plotLeft;
var xValue = chart.series[0].tooltipPoints[index];
// var xValue = evt.xAxis[0].value; // To use mouse position and not crosshair's position
var xAxis = evt.xAxis[0].axis;
$.each(xAxis.plotLinesAndBands,function(){
if(this.id===myPlotLineId)
{
this.destroy();
}
});
xAxis.addPlotLine({
value: xValue,
width: 1,
color: 'red',
//dashStyle: 'dash',
id: myPlotLineId
});
...
Add plot lines at click position # jsFiddle
Persist crosshair/cursor as plot lines on click # jsFiddle
You can do it in several ways
Highchart has a very cool renderer that allows you to add various graphics to the chart. One of the options is to add a path I will be illustrating the same here.
We shall reuse the path of the crosshair and add the same to the chart with some additional styles like color you mentioned. The path of the crosshair can be optained as this.tooltip.crosshairs[0].d this is in string form and can be easily converted to an array using the Array.split() function
click: function() {
this.renderer.path(this.tooltip.crosshairs[0].d.split(" ")).attr({
'stroke-width': 2,
stroke: 'red'
}).add();
}
This will accomplish adding the line. You can store the returned object into a global variable and then when you are about to add another such line, you can destroy the existing one by calling Element.destroy()
var line;
...
chart:{
events: {
click: function() {
if (line) {
line.destroy();
}
line = this.renderer.path(this.tooltip.crosshairs[0].d.split(" ")).attr({
'stroke-width': 2,
stroke: 'red'
}).add();
}
}
...
Persist tooltip / crosshair on click # jsFiddle
Assuming you don't have much meta data to be shown along with the line, this is the easiest (or the coolest :) ) approach. You can also attach meta data if you want to using the renderer's text object etc.
An alternate way could be adding vertical plotLines to the xAxis
UPDATE
Refer my other solution to this question, that would work with zoom,scroll,etc

Resources