I'm using the YUI 2.7 library to handle a dual-slider (range slider) control in a webpage.
It works great-- however, I wanted to allow users to switch the range values by Ajax-- effectively changing the price range from "0-50,000" to a subset (eg. "50-250") without reloading the page.
The problem is that it appears the values from the existing slider do not get reset, even when I explicitly set them back to NULL inside the function to "rebuild" the slider.
The slider handles appear out of position after the ajax request, (way off the scale to the right) and the values of the slider apparently randomly fluctuate.
Is there a way to explicitly destroy the YUI slider object, beyond setting its reference to null? Or do I just need to redeclare the scale and min/max values somehow?
Thanks for any help (I'll try to post a link to an example asap)
here's the code:
function slider(bg,minthumb,maxthumb,minvalue,maxvalue,startmin,startmax,aSliderName,soptions) {
var scaleFactor = null;
var tickSize = null;
var range = null;
var dual_slider = null;
var initVals = null;
var Dom = null;
range = options.sliderLength;
if ((startmax - startmin) < soptions.sliderLength) {
tickSize = (soptions.sliderLength / (startmax - startmin));
}else{
tickSize = 1;
}
initVals = [ 0,soptions.sliderLength ], // Values assigned during instantiation
//Event = YAHOO.util.Event,
dual_slider,
scaleFactor = ((startmax - startmin) / soptions.sliderLength);
dual_slider = YAHOO.widget.Slider.getHorizDualSlider(
bg,minthumb,maxthumb,range, tickSize, initVals);
dual_slider.subscribe("change", function(instance) {
priceMin = (dual_slider.minVal * scaleFactor) + startmin;
priceMax = (dual_slider.maxVal * scaleFactor) + startmin;
});
dual_slider.subscribe("slideEnd", function(){ alert(priceMin + ' ' + priceMax); });
return dual_slider;
}
Store the startmin, startmax, and scaleFactor on the dual_slider object, then in your ajax callback, update those properties with new values. Change your change event subscriber to reference this.startmin, this.startmax, and this.scaleFactor.
Slider and DualSlider only really understand the pixel offsets of the thumbs, and report the values as such. As you've done (and per most Slider examples), you need to apply a conversion factor to translate a pixel offset to a "value". This common idiom has been rolled into the core logic of the YUI 3 Slider (though there isn't yet a DualSlider in the library).
Here's an example that illustrates dynamically updating value ranges:
http://yuiblog.com/sandbox/yui/v282/examples/slider/slider_factor_change.html
Related
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.
I'm trying to trigger an event half-way through the progress (not time) of a transition. It sounds simple, but since the transition can have any curve it's quite tricky. In my particular case it's not going to be paused or anything so that consideration is out of the way.
(Simplified) essentially I could trigger an animation on a modifier like this:
function scaleModifierTo(stateModifier, scale, animationDuration) {
stateModifier.setTransform(
Transform.scale(scale, scale, scale),
{
duration: animationDuration,
curve: this.options.curve
}
);
}
When the interpolated state of the Transitionable hits 0.5 (half-way through) I want to trigger a function.
I haven't dug that deep behind in the source of famo.us yet, but maybe need to do something like
subclass something and add the possibility to listen when the state passes through a certain point?
reverse the curve defined and use a setTimeout (or try to find a proximity using a few iterations of the chosen curve algorithm (ew))
Is it possible to do this easily? What route should I go down?
I can think of a couple of ways to achieve such, and both lend to the use of Modifier over StateModifier. If you are new, and haven't really had the chance to explore the differences, Modifier consumes state from the transformFrom method which takes a function that returns a transform. This is where we can use our own Transitionable to supply state over the lifetime of our modifier.
To achieve what you wish, I used a Modifier with a basic transformFrom that will alter the X position of the surface based on the value of the Transitionable. I can then monitor the transitionable to determine when it is closest, or in my case greater than or equal to half of the final value. The prerender function will be called and checked on every tick of the engine, and is unbinded when we hit the target.
Here is that example..
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var Modifier = require('famous/core/Modifier');
var Transform = require('famous/core/Transform');
var Transitionable = require('famous/transitions/Transitionable');
var SnapTransition = require('famous/transitions/SnapTransition');
Transitionable.registerMethod('snap',SnapTransition);
var snap = { method:'snap', period:1000, damping:0.6};
var context = Engine.createContext();
var surface = new Surface({
size:[200,200],
properties:{
backgroundColor:'green'
}
});
surface.trans = new Transitionable(0);
surface.mod = new Modifier();
surface.mod.transformFrom(function(){
return Transform.translate(surface.trans.get(),0,0);
});
context.add(surface.mod).add(surface);
function triggerTransform(newValue, transition) {
var prerender = function(){
var pos = surface.trans.get();
if (pos >= (newValue / 2.0)) {
// Do Something.. Emit event etc..
console.log("Hello at position: "+pos);
Engine.removeListener('prerender',prerender);
}
}
Engine.on('prerender',prerender);
surface.trans.halt();
surface.trans.set(newValue,transition);
}
surface.on('click',function(){ triggerTransform(400, snap); });
The downside of this example is the fact that you are querying the transitionable twice. An alternative is to add your transitionable check right in the transformFrom method. This could get a bit strange, but essentially we are modifying our transformFrom method until we hit our target value, then we revert back to the original transformFrom method.. triggerTransform would be defined as follows..
Hope this helps!
function triggerTransform(newValue, transition) {
surface.mod.transformFrom(function(){
pos = surface.trans.get()
if (pos >= newValue/2.0) {
// Do something
console.log("Hello from position: " + pos)
surface.mod.transformFrom(function(){
return Transform.translate(surface.trans.get(),0,0);
});
}
return Transform.translate(pos,0,0)
})
surface.trans.set(newValue,transition);
}
Thank you for your responses, especially #johntraver for the prerender event, I wasn't aware of the existence of that event.
I realised it made more sense that I should handle this logic together with my move animation, not the scale one. Then, I ended up using a (very hacky) way of accessing the current state of the transition and by defining a threshold in px I can trigger my function when needed.
/**
* Move view at index to a specified offset
* #param {Number} index
* #param {Number} xOffset xOffset to move to
* #param {Object} animation Animation properties
* #return void
*/
function moveView(index, xOffset, animation) {
var rectModifier = this._views[index].modifiers.rect;
var baseXOffset = rectModifier._transformState.translate.state[0];
// After how long movement is reflow needed?
// for the sake of this example I use half the distance of the animation
var moveThreshold = Math.abs(baseXOffset - xOffset)/2;
/**
* Callback function triggered on each animation frame to see if the view is now covering
* the opposite so we can trigger a reflow of the z index
* #return void
*/
var prerender = function() {
var numPixelsMoved = Math.abs(baseXOffset - rectModifier._transformState.translate.state[0]);
if (numPixelsMoved > moveThreshold) {
Engine.removeListener('prerender', prerender);
// trigger a method when this is reached
_reflowZIndex.call(this);
}
}.bind(this);
rectModifier.setTransform(
Transform.translate(xOffset, 0, 0),
animation,
function() {
Engine.removeListener('prerender', prerender);
}
);
Engine.on('prerender', prerender);
}
Obviously the ._transformState.translate.state[0] is a complete hack, but I couldn't figure out of getting this value in a clean way without adding my own Transitionable, which I don't want. If there is a cleaner way of finding the current state as a Number between 0.0-1.0 that would be ace; anyone knows of one?
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.
I need to scan through every pixel in a canvas image and do some fiddling with the colors etc. For optimal performance, should I grab all the data in one go and work on it through the array? Or should I call each pixel as I work on it.
So basically...
data = context.getImageData(x, y, height, width);
VS
data = context.getImageData(x, y, 1, 1); //in a loop height*width times.
You'll get much higher performances by grabbing the image all at once since :
a) a (contiguous) acces to an array is way faster than a function call.
b) especially when this function isa method of a DOM object having some overhead.
c) and there might be buffer refresh issues that might delay response (if canvas is
on sight... or not depending on double buffering implementation).
So go for a one-time grab.
I'll suggest you look into Javascript Typed Arrays to get the most of the
imageData result.
If i may quote myself, look at how you can handle pixels fast in this old post of mine
(look after 2) ):
Nice ellipse on a canvas?
(i quoted the relevant part below : )
You can get a UInt32Array view on your ImageData with :
var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer32 = new Uint32Array(myGetImageData.data.buffer);
then sourceBuffer32[i] contains Red, Green, Blue, and transparency packed into one unsigned 32 bit int. Compare it to 0 to know if pixel is non-black ( != (0,0,0,0) )
OR you can be more precise with a Uint8Array view :
var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer8 = new Uint8Array(myGetImageData.data.buffer);
If you deal only with shades of grey, then R=G=B, so watch for
sourceBuffer8[4*i]>Threshold
and you can set the i-th pixel to black in one time using the UInt32Array view :
sourceBuffer32[i]=0xff000000;
set to any color/alpha with :
sourceBuffer32[i]= (A<<24) | (B<<16) | (G<<8) | R ;
or just to any color :
sourceBuffer32[i]= 0xff000000 | (B<<16) | (G<<8) | R ;
(be sure R is rounded).
Listening to #Ken's comment, yes endianness can be an issue when you start fighting with bits 32 at a time.
Most computer are using little-endian, so RGBA becomes ABGR when dealing with them 32bits a once.
Since it is the vast majority of systems, if dealing with 32bit integer assume this is the case,
and you can -for compatibility- reverse your computation before writing the 32 bits results on Big endian systems.
Let me share those two functions :
function isLittleEndian() {
// from TooTallNate / endianness.js. https://gist.github.com/TooTallNate/4750953
var b = new ArrayBuffer(4);
var a = new Uint32Array(b);
var c = new Uint8Array(b);
a[0] = 0xdeadbeef;
if (c[0] == 0xef) { isLittleEndian = function() {return true }; return true; }
if (c[0] == 0xde) { isLittleEndian = function() {return false }; return false; }
throw new Error('unknown endianness');
}
function reverseUint32 (uint32) {
var s32 = new Uint32Array(4);
var s8 = new Uint8Array(s32.buffer);
var t32 = new Uint32Array(4);
var t8 = new Uint8Array(t32.buffer);
reverseUint32 = function (x) {
s32[0] = x;
t8[0] = s8[3];
t8[1] = s8[2];
t8[2] = s8[1];
t8[3] = s8[0];
return t32[0];
}
return reverseUint32(uint32);
};
Additionally to what GameAlchemist said, if you want to get or set all the colors of a pixel simultaneously, but you don't want to check endianness, you can use a DataView:
var data = context.getImageData(0, 0, canvas.width, canvas.height);
var view = new DataView(data.data.buffer);
// Read or set pixel (x,y) as #RRGGBBAA (big endian)
view.getUint32(4 * (x + y*canvas.width));
view.setUint32(4 * (x + y*canvas.width), 0xRRGGBBAA);
// Read or set pixel (x,y) as #AABBGGRR (little endian)
view.getUint32(4 * (x + y*canvas.width), true);
view.setUint32(4 * (x + y*canvas.width), 0xAABBGGRR, true);
// Save changes
ctx.putImageData(data, 0, 0);
It depends on what exactly you're doing, but I'd suggest grabbing it all at once, and then looping through it.
Grabbing it all at once is faster than grabbing it pixel by pixel, since searching through an array is a lot faster than searching through a canvas, once for each pixel.
If you're really in need of speed, look into web workers. You can set each one to grab a specific section of the canvas, and since they can run simultaneously, they'll make much better use out of your CPU.
getImageData() isn't really slow enough for you to notice the difference if you were to grab it all at once or individually, in my experiences using the function.
I'm using the YUI2 colour picker on a project to provide a theme/colour scheme changing functionality. I'm setting the default rgb value of each colour picker to the current rgb value of an element of the colour scheme.
The rgb value that the picker holds is fine, however the Hue Slider and Picker Slider are not updating to reflect this. Whenever the colour picker appears the hue and picker are set to 0 and ffffff respectively.
I've searched through the documentation and tried a few likely methods that might update the hue/picker slider appropriately, with no luck.
Any advice would be greatly appreciated
After some more searching and playing around I managed to come up with a solution. There are two steps to the process;
Step 1
You need to compute the HSV (Hue, Saturation, Value) from the rgb value. This is done using the function rgbTohsv() below, which I got here. Its been modified from the original to account for the way the YUI HueSlider and PickerSlider expect input.
function rgbTohsv (rgb) {
var computedH = computedS = computedV = 0;
//remove spaces from input RGB values, convert to int
var r = parseInt( (''+rgb[0]).replace(/\s/g,''),10 );
var g = parseInt( (''+rgb[1]).replace(/\s/g,''),10 );
var b = parseInt( (''+rgb[2]).replace(/\s/g,''),10 );
r=r/255; g=g/255; b=b/255;
var minRGB = Math.min(r,Math.min(g,b));
var maxRGB = Math.max(r,Math.max(g,b));
// Black-gray-white
if (minRGB==maxRGB) {
computedV = minRGB;
return [0,0,Math.round(computedV*100)];
}
// Colors other than black-gray-white:
var d = (r==minRGB) ? g-b : ((b==minRGB) ? r-g : b-r);
var h = (r==minRGB) ? 3 : ((b==minRGB) ? 1 : 5);
computedH = 60*(h - d/(maxRGB - minRGB));
computedS = (maxRGB - minRGB)/maxRGB;
computedV = maxRGB;
return [(360-computedH)/2,Math.round(computedS*100),Math.round(computedV*100)];
}
Modifications:: The hue slider expects a value between 180-0, rather than the usual 0-360. As well as the shorter range it is also inverted so it goes from 180->0. The S and V values must be an integer between 0-100, whereas the original function returned 0-1.0 values. Both of these issues have been accounted for in the above function.
Step 2
Next you need to set the hsv values of the colour picker., so where colourPicker is your initialised colour picker variable;
hsv = rgbTohsv(rgb);
colourPicker.hueSlider.setValue(hsv[0],0);
colourPicker.pickerSlider.setRegionValue(hsv[1],hsv[2]);
By default updating the hue slider and picker slider do an animation when updated, you can suppress this by adding a false variable at the end of the method call.
colourPicker.hueSlider.setValue(hsv[0],0,true);
colourPicker.pickerSlider.setRegionValue(hsv[1],hsv[2], true);