Get correct percent in dimplejs vertical 100% bar chart - dimple.js

I have created a vertical 100% bar chart similar to this example but with the Titanic dataset. The default tooltip shows the correct percentages but my modified tooltip does not. In the figure below, the tooltip should show 73%, which is the percentage of women that survived, instead of 100%.
It seems that my code is giving me the correct percent as it is aggregating with respect to the variable Sex instead of along the dimension of Survived/Did not survive. Code snippets below. Variable 'source' is defined by clicking on the menu.
d3.tsv("data/titanic.tsv", function(data) {
var myChart = new dimple.chart(svg, data);
var x = myChart.addCategoryAxis("x", source);
x.addOrderRule(source);
var y = myChart.addPctAxis("y", "count");
var mySeries = myChart.addSeries(["Survived"], dimple.plot.bar);
And for the tooltip:
mySeries.addEventHandler("mouseover", function (e) {
var cx = parseFloat(e.selectedShape.attr("x"));
var cxWidth = parseFloat(e.selectedShape.attr("width"));
var cy = parseFloat(e.selectedShape.attr("y"));
// set size and coordinates of tooltip
var width = 120;
var height = 50;
var xPop = (cx +width + 10 < svg.attr("width")) ? cx: cx ;
var yPop = (cy - height / 2 < 0) ? 25: cy - height / 2 + 35;
popup = svg.append("g");
// change style of tooltip
popup
.append("rect")
.attr("x", xPop + 5)
.attr("y", yPop - 5)
.attr("width", 150)
.attr("height", height)
.attr("width", width)
.attr("rx", 5)
.attr("ry", 5)
.style("fill", "white")
.style("stroke", "#36b0b6")
.style("stroke-width", 2);
//add appropriate text to tooltip
popup
.append('text')
.attr('x', xPop + 10)
.attr('y', yPop + 10)
.append('tspan')
.attr('x', xPop + 10)
.attr('y', yPop + 20)
.text(e.seriesValue[0])
.style("font-family", "sans-serif")
.style("font-size", 10)
.append('tspan')
.attr('x', xPop + 10)
.attr('y', yPop + 40)
.text("Percent: " + d3.format(",.0f")(e.yValue *100))
.style("font-family", "sans-serif")
.style("font-size", 10);
});
With acknowledgements to Anna Pawlicka for original tooltip code. I have played around with yValue, seriesValue, and aggField. I can get the correct percent to show up by hard coding via something like
if (e.xValue==="Female" && e.seriesValue[0]==="Survived") {
var t = 337/464 }
but that is not the most elegant solution.
Any ideas on what I am missing?

The way you've done it makes sense, but there's an oversight in the eventArgs which causes it to send the running total rather than the total for the segment. You can work around it by applying the event handler with the d3 method. It's important that you move the code after the draw function is called, at which point you can get access to the d3 shapes and apply an event handler with on:
// Must draw first
myChart.draw();
// Can now access series.shapes for d3 stuff
mySeries.shapes.on("mouseover", function (d) {
// d contains the full data element as used by dimple's internal methods
var cx = parseFloat(e.selectedShape.attr("x"));
var cxWidth = parseFloat(e.selectedShape.attr("width"));
var cy = parseFloat(e.selectedShape.attr("y"));
// set size and coordinates of tooltip
var width = 120;
var height = 50;
var xPop = (cx +width + 10 < svg.attr("width")) ? cx: cx ;
var yPop = (cy - height / 2 < 0) ? 25: cy - height / 2 + 35;
popup = svg.append("g");
// change style of tooltip
popup
.append("rect")
.attr("x", xPop + 5)
.attr("y", yPop - 5)
.attr("width", 150)
.attr("height", height)
.attr("width", width)
.attr("rx", 5)
.attr("ry", 5)
.style("fill", "white")
.style("stroke", "#36b0b6")
.style("stroke-width", 2);
//add appropriate text to tooltip
popup
.append('text')
.attr('x', xPop + 10)
.attr('y', yPop + 10)
.append('tspan')
.attr('x', xPop + 10)
.attr('y', yPop + 20)
.text(e.seriesValue[0])
.style("font-family", "sans-serif")
.style("font-size", 10)
.append('tspan')
.attr('x', xPop + 10)
.attr('y', yPop + 40)
// Now you just need to use height instead of yValue
.text("Percent: " + d3.format("%")(e.height))
.style("font-family", "sans-serif")
.style("font-size", 10);
});
The code is the same as yours apart from using on instead of addEventHandler and using height instead of yValue.

Related

D3 pie chart legends with horizontal pagination

I have a requirement where i need to show pagination for legends if the number of legends is more than two. I am using d3 pie chart. Any help would be appreciated.
I have found out something similar but unable to implement using d3 version 5.
I have modified the code you gave in this bl.ocks link.
Here is a link of the codepen I created link.
The pagination implemented here could be directly used with d3 v5. The problem was coming up with the chart.
var legendCount = dataSet.series.length;
var legendWidth = 10;
var legendSpacing = 6;
var netLegendHeight = (legendWidth + legendSpacing) * legendCount;
var legendPerPage, totalPages, pageNo;
// if (netLegendHeight / height > 1) {
legendPerPage = 2;
totalPages = Math.ceil(legendCount / legendPerPage);
pageNo = 1;
var startIndex = (pageNo - 1) * legendPerPage;
var endIndex = startIndex + legendPerPage;
var seriesSubset = [],
colorSubset = [];
for (var i = 0; i < dataSet.series.length; i++) {
if (i >= startIndex && i < endIndex) {
seriesSubset.push(dataSet.series[i]);
colorSubset.push(colors[i]);
}
}
DrawLegendSubset(seriesSubset, colorSubset, legendPerPage, pageNo, totalPages);
// }
function DrawLegendSubset(seriesSubset, colorSubset, legendPerPage, pageNo, totalPages) {
var legend = svg.selectAll("g.legendg")
.data(seriesSubset)
.enter().append("g")
.attr('class', 'legendg')
.attr("transform", function(d, i) {
return "translate(" + (width - 40) + "," + i * (legendWidth + legendSpacing) + ")";
});
legend.append("rect")
.attr("x", 45)
.attr("width", legendWidth)
.attr("height", legendWidth)
.attr("class", "legend")
.style('fill', function(d, i) {
return colorSubset[i];
});
legend.append("text")
.attr("x", 60)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) {
return d;
});
var pageText = svg.append("g")
.attr('class', 'pageNo')
.attr("transform", "translate(" + (width + 7.5) + "," + (legendPerPage + 1) * (legendWidth + legendSpacing) + ")");
pageText.append('text').text(pageNo + '/' + totalPages)
.attr('dx', '.25em');
var prevtriangle = svg.append("g")
.attr('class', 'prev')
.attr("transform", "translate(" + (width + 5) + "," + (legendPerPage + 1.5) * (legendWidth + legendSpacing) + ")")
.on('click', prevLegend)
.style('cursor', 'pointer');
var nexttriangle = svg.append("g")
.attr('class', 'next')
.attr("transform", "translate(" + (width + 20) + "," + (legendPerPage + 1.5) * (legendWidth + legendSpacing) + ")")
.on('click', nextLegend)
.style('cursor', 'pointer');
nexttriangle.append('polygon')
.style('stroke', '#000')
.style('fill', '#000')
.attr('points', '0,0, 10,0, 5,5');
prevtriangle.append('polygon')
.style('stroke', '#000')
.style('fill', '#000')
.attr('points', '0,5, 10,5, 5,0');
if (pageNo == totalPages) {
nexttriangle.style('opacity', '0.5')
nexttriangle.on('click', '')
.style('cursor', '');
} else if (pageNo == 1) {
prevtriangle.style('opacity', '0.5')
prevtriangle.on('click', '')
.style('cursor', '');
}
}
function prevLegend() {
pageNo--;
svg.selectAll("g.legendg").remove();
svg.select('.pageNo').remove();
svg.select('.prev').remove();
svg.select('.next').remove();
var startIndex = (pageNo - 1) * legendPerPage;
var endIndex = startIndex + legendPerPage;
var seriesSubset = [],
colorSubset = [];
for (var i = 0; i < dataSet.series.length; i++) {
if (i >= startIndex && i < endIndex) {
seriesSubset.push(dataSet.series[i]);
colorSubset.push(colors[i]);
}
}
DrawLegendSubset(seriesSubset, colorSubset, legendPerPage, pageNo, totalPages);
}
function nextLegend() {
pageNo++;
svg.selectAll("g.legendg").remove();
svg.select('.pageNo').remove();
svg.select('.prev').remove();
svg.select('.next').remove();
var startIndex = (pageNo - 1) * legendPerPage;
var endIndex = startIndex + legendPerPage;
var seriesSubset = [],
colorSubset = [];
for (var i = 0; i < dataSet.series.length; i++) {
if (i >= startIndex && i < endIndex) {
seriesSubset.push(dataSet.series[i]);
colorSubset.push(colors[i]);
}
}
DrawLegendSubset(seriesSubset, colorSubset, legendPerPage, pageNo, totalPages);
}
Here is an image of the final pie chart with pagination:

zoomable bar chart using d3.js

Trying to apply the following: https://bl.ocks.org/mbostock/4015254, on to my dataset. I have changed the variables to fit my dataset.
My code is the following:
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 60},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseDate = d3.timeParse("%Y-%m-%d"),
formatDate = d3.timeFormat("%Y");
var x = d3.scaleTime()
.domain([new Date(2002, 0, 1), new Date(2003, 0, 1)])
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var area = d3.area()
.curve(d3.curveStepAfter)
.y0(y(0))
.y1(function(d) { return y(d.value); });
var areaPath = g.append("path")
.attr("clip-path", "url(#clip)")
.attr("fill", "steelblue");
var yGroup = g.append("g");
var xGroup = g.append("g")
.attr("transform", "translate(0," + height + ")");
var zoom = d3.zoom()
.scaleExtent([1 / 4, 8])
.translateExtent([[-width, -Infinity], [2 * width, Infinity]])
.on("zoom", zoomed);
var zoomRect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", "none")
.attr("pointer-events", "all")
.call(zoom);
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
d3.json("api.php", function(d) {
d.date = parseDate(d.date);
d.value = +d.close;
return d;
}, function(error, data) {
if (error) throw error;
var xExtent = d3.extent(data, function(d) { return d.date; });
zoom.translateExtent([[x(xExtent[0]), -Infinity], [x(xExtent[1]), Infinity]])
y.domain([0, d3.max(data, function(d) { return d.value; })]);
yGroup.call(yAxis).select(".domain").remove();
areaPath.datum(data);
zoomRect.call(zoom.transform, d3.zoomIdentity);
});
function zoomed() {
var xz = d3.event.transform.rescaleX(x);
xGroup.call(xAxis.scale(xz));
areaPath.attr("d", area.x(function(d) { return xz(d.date); }));
}
</script>
Yet I am getting an error on my console with the following error message:
d3.v4.min.js:2 Uncaught TypeError: Cannot read property 'length' of undefined
at SVGPathElement.t (d3.v4.min.js:2)
at SVGPathElement.<anonymous> (d3.v4.min.js:2)
at ut.each (d3.v4.min.js:2)
at ut.attr (d3.v4.min.js:2)
at SVGRectElement.zoomed (research.php:123)
at k.apply (d3.v4.min.js:2)
at it (d3.v4.min.js:2)
at a.emit (d3.v4.min.js:2)
at a.zoom (d3.v4.min.js:2)
at d3.v4.min.js:2
An excerpt of my dataset looks like this:
[{"id":"1","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2006-12-29","time":"15:00:00.000000","close":"2388.023438000000000000","volume":"23700.000000000000000000","active":"1","exchange_id":"0"},{"id":"2","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-04","time":"15:00:00.000000","close":"2416.452637000000000000","volume":"16500.000000000000000000","active":"1","exchange_id":"0"},{"id":"3","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-05","time":"15:00:00.000000","close":"2369.071045000000000000","volume":"45400.000000000000000000","active":"1","exchange_id":"0"},{"id":"4","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-09","time":"15:00:00.000000","close":"2388.023438000000000000","volume":"28800.000000000000000000","active":"1","exchange_id":"0"},{"id":"5","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-10","time":"15:00:00.000000","close":"2369.071045000000000000","volume":"27800.000000000000000000","active":"1","exchange_id":"0"},{"id":"6","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-11","time":"15:00:00.000000","close":"2369.071045000000000000","volume":"25500.000000000000000000","active":"1","exchange_id":"0"},{"id":"7","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-12","time":"15:00:00.000000","close":"2378.546875000000000000","volume":"28100.000000000000000000","active":"1","exchange_id":"0"},{"id":"8","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-15","time":"15:00:00.000000","close":"2397.500244000000000000","volume":"23400.000000000000000000","active":"1","exchange_id":"0"}]
It consists of 2831 records. I don't understand why even my x and y axes aren't printing correctly. Thanks in advance.
The bl.ocks that you refer to uses a d3.csv which (if you look at the docs) has an accessor function that is passed each row of the data and then the whole date is accessed in the callback. Here's the accessor function:
function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
return d;
}
But in case of d3.json, there's no such accessor: d3.json(url[, callback]) which means you'll have to parse each row within the callback. Here's how:
d3.json("test.json", function(data) {
data.forEach(e => {
e.date = parseDate(e.date);
e.value = +e.close;
});
var xExtent = d3.extent(data, function(d) { return d.date; });
.....
Here's a fork of your code (I'm not sure why you have the file named as "api.php" when it is a JSON file. I've used a "test.json" file.
https://bl.ocks.org/shashank2104/21358032ac5507f7a6b7d620b1a4ef69/12b6089547e3b07edeca6214834d7e7a340be6a7
If you're unable to view that or if you're looking for a code snippet, here's one:
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 60},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseDate = d3.timeParse("%Y-%m-%d"),
formatDate = d3.timeFormat("%Y");
var x = d3.scaleTime()
.domain([new Date(2006, 12, 1), new Date(2007, 1, 1)])
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var area = d3.area()
.curve(d3.curveStepAfter)
.y0(y(0))
.y1(function(d) { return y(d.value); });
var areaPath = g.append("path")
.attr("clip-path", "url(#clip)")
.attr("fill", "steelblue");
var yGroup = g.append("g");
var xGroup = g.append("g")
.attr("transform", "translate(0," + height + ")");
var zoom = d3.zoom()
.scaleExtent([1 / 4, 8])
.translateExtent([[-width, -Infinity], [2 * width, Infinity]])
.on("zoom", zoomed);
var zoomRect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", "none")
.attr("pointer-events", "all")
.call(zoom);
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var data = [{"id":"1","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2006-12-29","time":"15:00:00.000000","close":"2388.023438000000000000","volume":"23700.000000000000000000","active":"1","exchange_id":"0"},{"id":"2","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-04","time":"15:00:00.000000","close":"2416.452637000000000000","volume":"16500.000000000000000000","active":"1","exchange_id":"0"},{"id":"3","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-05","time":"15:00:00.000000","close":"2369.071045000000000000","volume":"45400.000000000000000000","active":"1","exchange_id":"0"},{"id":"4","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-09","time":"15:00:00.000000","close":"2388.023438000000000000","volume":"28800.000000000000000000","active":"1","exchange_id":"0"},{"id":"5","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-10","time":"15:00:00.000000","close":"2369.071045000000000000","volume":"27800.000000000000000000","active":"1","exchange_id":"0"},{"id":"6","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-11","time":"15:00:00.000000","close":"2369.071045000000000000","volume":"25500.000000000000000000","active":"1","exchange_id":"0"},{"id":"7","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-12","time":"15:00:00.000000","close":"2378.546875000000000000","volume":"28100.000000000000000000","active":"1","exchange_id":"0"},{"id":"8","exchange_symbol":"TSE","currency":"JPY","stock_id":"1","stock_name":"KYOKUYO CO.,LTD.","stock_symbol":"1301.T","date":"2007-01-15","time":"15:00:00.000000","close":"2397.500244000000000000","volume":"23400.000000000000000000","active":"1","exchange_id":"0"}];
data.forEach(e => {
e.date = parseDate(e.date);
e.value = +e.close;
});
var xExtent = d3.extent(data, function(d) { return d.date; });
x.domain(xExtent);
zoom.translateExtent([[x(xExtent[0]), -Infinity], [x(xExtent[1]), Infinity]])
y.domain([0, d3.max(data, function(d) { return d.value; })]);
yGroup.call(yAxis).select(".domain").remove();
areaPath.datum(data);
zoomRect.call(zoom.transform, d3.zoomIdentity);
function zoomed() {
var xz = d3.event.transform.rescaleX(x);
xGroup.call(xAxis.scale(xz));
areaPath.attr("d", area.x(function(d) { return xz(d.date); }));
}
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
Hope this makes sense. Also, I'm setting the x domain to the xExtent that is computed from the data. Play around with it to see the difference.
Somehow the load function is of a different type and uses the following format.
d3.json("api.php", function(data) {
data.forEach(e => {
e.date = parseDate(e.date);
e.value = +e.close;
});
var xExtent = d3.extent(data, function(d) { return d.date; });
zoom.translateExtent([[x(xExtent[0]), -Infinity], [x(xExtent[1]), Infinity]])
y.domain([0, d3.max(data, function(d) { return d.value; })]);
yGroup.call(yAxis).select(".domain").remove();
areaPath.datum(data);
zoomRect.call(zoom.transform, d3.zoomIdentity);
});

Vertical drag (touchMove) a titanium appcelerator view

I want to drag vertically a view in titanium appcelerator and stop when entire view height is shown on the screen.
At start the view has a height of 140 and a bottom of -120 so i have can a piece of a view that i can start to drag :
This is how i want to vertically drag "myView", x axis is left to 0 so i can only drag it from bottom to top, the aim is to stop dragging when all myView height is shown (all myView is visible)
.
var WIDTH = (OS_ANDROID) ? Ti.Platform.displayCaps.platformWidth / dpi : Ti.Platform.displayCaps.platformWidth;
var HEIGHT = (OS_ANDROID) ? Ti.Platform.displayCaps.platformHeight / dpi : Ti.Platform.displayCaps.platformHeight;
var sx = 0;
var sy = 0;
var cx = 0;
var cy = 0;
var xDistance = 0;
function onTouchStart(e) {
// start movement
sx = e.x;
sy = e.y;
cx = e.x;
cy = e.y;
console.log(e.x);
}
function onTouchMove(e) {
xDistance = cx - sx;
var yDistance = cy - sy;
var points = e.source.convertPointToView({
x: e.x,
y: e.y
}, $.single);
$.myView.applyProperties({
left: 0,
top: points.y,
opacity: 1
});
/*------------------------------------------------------
* HERE I TRY TO DETECT IF myView HEIGHT IS SHOWN ENTIRELY
* is myView.top + myView.height > viewport ?
------------------------------------------------------*/
var t = $.myView.top + $.myView.height;
if( t >= HEIGHT ){
alert('all myView is visible now ???');
return; // <= why this has no effect ??
}
cx = e.x;
cy = e.y;
}
function onTouchEnd(e) {
// check xDistance
}
$.myView.addEventListener("touchmove", onTouchMove);
$.myView.addEventListener("touchstart", onTouchStart);
$.myView.addEventListener("touchend", onTouchEnd);
With this code, i can drag vertically myView but it's not stops when this height is entirely shown.
My first question is :
- How can i say if myView is shown entirely?
- How to disable vertically drag to top if all of myView height is shown?
Thank you
Function onTouchMove should be like this.
function onTouchMove(e) {
/*------------------------------------------------------
* HERE I TRY TO DETECT IF myView HEIGHT IS SHOWN ENTIRELY
* is myView.top + myView.height > viewport ?
------------------------------------------------------*/
var t = $.myView.top + $.myView.height;
if( t >= HEIGHT ){
alert('all myView is visible now ???');
return; // <= why this has no effect ??
}
xDistance = cx - sx;
var yDistance = cy - sy;
var points = e.source.convertPointToView({
x: e.x,
y: e.y
}, $.single);
$.myView.applyProperties({
left: 0,
top: points.y,
opacity: 1
});
cx = e.x;
cy = e.y;
}

Raphael-js drag and click events inaccurate after browser resize

I've created a radial dial using the Raphael-js library and it works as should on page load. It's embedded in a responsive layout so I want it to resize according to it's container, which it does. However, the new container size makes the mouse events inaccurate. When I resize it back to what it was on page load, it works fine.
function RadialDial(paperId, opts) {
var thisObj = this;
this.dialParent = document.querySelector(paperId);
this.divPaper = this.dialParent.querySelector('.radialDial');
this.divPaperW = this.divPaper.clientWidth;
this.scaleRatio = this.divPaperW / 250;
this.outputEle = this.dialParent.querySelector('.dialOutput .val');
this.btnPlus = this.dialParent.querySelector('.btnPlus');
this.btnMinus = this.dialParent.querySelector('.btnMinus');
this.debug = this.dialParent.querySelector('.debug');
this.opts = {
dialCenter: this.divPaperW / 2,
dialRadius: this.divPaperW / 2,
startA: 155,
endA: 25,
arcCentralA: 230,
maxRange: 12,
minRange: 3,
postText: false,
rangeSteps: 3
}
this.currNeedleA;
this.rangeAngles = [];
this.setOptions(opts);
this.paper = Raphael(this.divPaper, this.opts.dialRadius * 2, this.opts.dialRadius * 2);
this.rangeDivisions = Raphael.rad(this.opts.arcCentralA / (this.opts.maxRange - this.opts.minRange));
this.arcStartX = (this.opts.dialCenter + ((this.opts.dialRadius - (30 * this.scaleRatio)) * Math.cos(Raphael.rad(this.opts.startA)))).toString();
this.arcStartY = (this.opts.dialCenter + ((this.opts.dialRadius - (30 * this.scaleRatio)) * Math.sin(Raphael.rad(this.opts.startA)))).toString();
var currSectorX = this.arcStartX;
var currSectorY = this.arcStartY;
var dialFaceAtts = (Raphael.svg) ? {fill: "r#ffffff-#ffffff:85-#999999:75-#cccccc:57-#999999", stroke: "none"} : {fill: "#ffffff", stroke: "#999999", "stroke-width": (1 * this.scaleRatio)};
this.dialFace = this.paper.circle(this.opts.dialCenter, this.opts.dialCenter, this.opts.dialRadius).attr(dialFaceAtts);
var dialFaceRim = this.paper.circle(this.opts.dialCenter, this.opts.dialCenter, (102 * this.scaleRatio)).attr({fill: "none", "stroke-width": (8 * this.scaleRatio), stroke: "#eeeeee", "stroke-opacity": 0.4});
var currSectorAngle = Raphael.rad(this.opts.startA);
var rangeSet = this.paper.set();
for (var i = this.opts.minRange; i <= (this.opts.maxRange); i++) {
currSectorX = (this.opts.dialCenter + ((this.opts.dialRadius - (40 * this.scaleRatio)) * Math.cos(currSectorAngle))).toString();
currSectorY = (this.opts.dialCenter + ((this.opts.dialRadius - (40 * this.scaleRatio)) * Math.sin(currSectorAngle))).toString();
if (i % this.opts.rangeSteps == 0) {
var rangeTxt = this.paper.text(currSectorX, currSectorY, i).attr({fill: "#00a2d8", "font-size": (22 * this.scaleRatio).toString()});
rangeSet.push(rangeTxt);
this.rangeAngles[i] = Raphael.deg(this.rangeDivisions * (i - (this.opts.minRange)));
}
currSectorAngle = currSectorAngle + this.rangeDivisions;
}
this.clickArea = this.paper.circle(this.opts.dialCenter, this.opts.dialCenter, this.opts.dialRadius).attr({fill: "red", "fill-opacity": 0, stroke: "none"});
this.needle = this.paper.path("M" + (this.arcStartX).toString() + "," + (this.arcStartY).toString() +
"L" + (this.opts.dialCenter * (138.89401 / this.opts.dialCenter) * this.scaleRatio).toString() + "," + (this.opts.dialCenter * (107.45764 / this.opts.dialCenter) * this.scaleRatio).toString() +
"L" + (this.opts.dialCenter * (147.34637 / this.opts.dialCenter) * this.scaleRatio).toString() + "," + (this.opts.dialCenter * (125.5838 / this.opts.dialCenter) * this.scaleRatio).toString() + "z").attr({fill: '#0058b6', stroke: "none"});/* */
var needleLine = this.paper.path("M" + (this.opts.dialCenter + (18 * this.scaleRatio)).toString() + ' ' + (this.opts.dialCenter - (8 * this.scaleRatio)).toString() + ", L" + this.arcStartX + "," + this.arcStartY).attr({stroke: "#ffffff", "stroke-width": .7});
var centerCircle = this.paper.circle(this.opts.dialCenter, this.opts.dialCenter, (12 * this.scaleRatio)).attr({fill: "#0058b6", stroke: "none"});
this.needleSet = this.paper.set();
this.needleSet.push(this.needle, needleLine);
this.dialSet = this.paper.set();
this.dialSet.push(dialFaceRim, this.dialFace, this.clickArea, this.needleSet, rangeSet, centerCircle, needleLine);
this.paper.setViewBox(0, 0, this.opts.dialRadius * 2, this.opts.dialRadius * 2, true);
this.paper.canvas.setAttribute('preserveAspectRatio', 'none');
this.needleSet.push(this.needle);
this.needleSet.data('thisObj', thisObj);
this.needleSet.data('paperObj', this.paper.canvas);
this.setNeedleDrag();
this.dialFaceClick();
}
RadialDial.prototype = {
constructor: RadialDial,
setOptions: function (opts) {
for (key in opts) {
if (!opts.hasOwnProperty(key)) {
continue;
}
this.opts[key] = opts[key];
}
},
drawDial: function () {
},
elePosition: function (ele) {
var eleX = 0;
var eleY = 0;
while (ele) {
eleX += (ele.offsetLeft - ele.scrollLeft + ele.clientLeft);
eleY += (ele.offsetTop - ele.scrollTop + ele.clientTop);
ele = ele.offsetParent;
}
return {x: eleX, y: eleY};
},
moveNeedle: function (dx, dy, x, y, e) {
var classObj = this.data('thisObj');
var rectObject = classObj.divPaper.getBoundingClientRect();
var paperXY = classObj.elePosition(classObj.divPaper);
var mouseX, mouseY;
mouseX = e.clientX - rectObject.left;
mouseY = e.clientY - rectObject.top;
var needleA = Raphael.angle(classObj.opts.dialCenter, classObj.opts.dialCenter, classObj.needle.getPointAtLength(classObj.needle.getTotalLength())['x'], classObj.needle.getPointAtLength(classObj.needle.getTotalLength())['y']);
var newA = Raphael.angle(classObj.opts.dialCenter, classObj.opts.dialCenter, mouseX, mouseY);
var rotateAngle = (360 - needleA) + newA;
if (!(newA > (360 - classObj.opts.startA) && newA < (360 - classObj.opts.endA))) {
classObj.needleSet.transform('r' + rotateAngle + "," + classObj.opts.dialCenter + "," + classObj.opts.dialCenter);
}
},
setNeedleDrag: function () {
var startDrag = function () {
}, dragger = this.moveNeedle,
endDrag = this.findNearestStep;
this.needleSet.drag(dragger, startDrag, endDrag);
},
dialFaceClick: function () {
var classObj = this;
this.clickArea.node.onclick = function (e) {
var e = e || window.event;
var rectObject = classObj.divPaper.getBoundingClientRect();
var mouseX, mouseY;
mouseX = e.clientX - rectObject.left;
mouseY = e.clientY - rectObject.top;
var needleA = Raphael.angle(classObj.opts.dialCenter, classObj.opts.dialCenter, classObj.needle.getPointAtLength(classObj.needle.getTotalLength())['x'], classObj.needle.getPointAtLength(classObj.needle.getTotalLength())['y']);
var newA = Raphael.angle(classObj.opts.dialCenter, classObj.opts.dialCenter, mouseX, mouseY);
var rotateAngle = (360 - needleA) + newA;
if (!(newA > (360 - classObj.opts.startA) && newA < (360 - classObj.opts.endA))) {
classObj.needleSet.transform('r' + rotateAngle + "," + classObj.opts.dialCenter.toString() + "," + classObj.opts.dialCenter.toString());
}
classObj.findNearestStep(classObj);
return false;
}
},
findNearestStep: function (obj) {
var classObj = (obj.target || obj.srcElement) ? this.data('thisObj') : obj;
var currVal = Math.round((Raphael.rad(classObj.needle.matrix.split().rotate) * ((classObj.opts.maxRange - classObj.opts.minRange) / Raphael.rad(classObj.opts.arcCentralA))) + classObj.opts.minRange);
var nextVal = currVal;
var prevVal, newA, index;
if (currVal % classObj.opts.rangeSteps != 0) {
while (nextVal % classObj.opts.rangeSteps != 0) {
nextVal = nextVal + 1;
}
if ((nextVal - currVal) > (classObj.opts.rangeSteps / 2)) {
nextVal = nextVal - classObj.opts.rangeSteps;
}
index = nextVal;
} else {
index = currVal;
}
newA = classObj.rangeAngles[index];
classObj.needleSet.transform('r' + (newA) + "," + classObj.opts.dialCenter + "," + classObj.opts.dialCenter);
}
}
Here is my fiddle, http://jsfiddle.net/fiddle_fish/rvLo1cuy/ , dragging the needle makes it follow the mouse pointer closely. Now click the "Resize container" link, whilst the needle still moves it doesn't follow the pointer closely. It seems the resize has created an offset for the mouse event target area.
I've tried changing the viewbox settings, width/height values,removing events and reapplying them, deleting the dial on resize and redrawing the dial but nothing works.
Tried, raphael js, calculate setViewBox width height to fix window
and, raphael js, resize canvas then setViewBox to show all elements
Neither works. :(
I sussed this out. I've multiplied the mouse x-y coordinates with a ratio based on the paper size onload/resize. Works a treat :)
I just encountered the same issue. Basically, on window resize I recalculate the "scale" i.e. the ratio of the svg element's viewBox to its current height/width.
Here's my solution:
var scale = {
x:1,
y:1
}
$(window).resize(function(){
scale = getScale(paper);
})
function getScale(paper){
var x = paper.canvas.viewBox.baseVal.width/$(paper.canvas).width();
var y = paper.canvas.viewBox.baseVal.height/$(paper.canvas).height();
return {
x:x,
y:y
}
}
and then in my "move" function, I added a multiplier to dx and dy:
var move = function (dx, dy,x,y) {
var X = this.cx + dx * scale.x,
Y = this.cy + dy * scale.y;
this.attr({cx: X, cy: Y});
}
For context, the move function is used like so:
var start = function(){
this.cx = this.attr("cx"),
this.cy = this.attr("cy");
}, move = function (dx, dy,x,y) {
var X = this.cx + dx * scale.x,
Y = this.cy + dy * scale.y;
this.attr({cx: X, cy: Y});
}
raphael_element.drag(move,start);
Note that there's some JQuery thrown in here that you could easily do without.

how to change background image dynamically on text field

I do the following
When I tap on text field, I show a picker, and I change the background image of the text field.
When the person is done with the picker (they click "done"), and I change the text field background back to white.
When I click on the text field again, the background image does not get changed even though the picker appears.
What can I do? I want it so that the text field keeps getting changed to a green background whenever I click on it and the picker shows up. And then changes back to white when I tap out of it and the picker disappears.
tf1.addEventListener('focus', function(e) {
Titanium.API.info('Triggered focus on date txtField');
tf1.setBackgroundImage('../images/selected.gif');
Titanium.API.info(tf1.backgroundImage);
var winDatePicker1 = Titanium.UI.currentWindow;
var minDate = new Date();
minDate.getFullYear();
minDate.getMonth();
minDate.getDate();
var maxDate = new Date();
maxDate.setFullYear(2018);
maxDate.setMonth(9);
maxDate.setDate(30);
var datePicker = Ti.UI.createPicker({
type : Ti.UI.PICKER_TYPE_DATE_AND_TIME,
minDate : minDate,
maxDate : maxDate,
bottom : 0,
minuteInterval : 10
});
var tview = Ti.UI.createView({
height : '100%',
width : '100%',
top : 0,
backgroundColor : 'transparent'
});
var done1 = Ti.UI.createImageView({
title : '',
width : 80,
right : 12,
height : 40,
image : '../images/done.gif',
});
var toolbar1 = Ti.UI.createView({
height : 43,
backgroundImage : '../images/toolbar.gif',
});
toolbar1.add(done1);
done1.addEventListener('click', function() {
tf1.backgroundImage = '';
toolbar1.hide();
datePicker.hide();
tview.hide();
});
tview.addEventListener('click', function() {
toolbar1.hide();
datePicker.hide();
tview.hide();
});
// turn on the selection indicator (off by default)
datePicker.selectionIndicator = true;
datePicker.addEventListener('change', function(e) {
Titanium.API.info('E value = ' + e.value);
var pickerDate = e.value;
var day = pickerDate.getDate();
day = day.toString();
if (day.length < 2) {
day = '0' + day;
}
var month = pickerDate.getMonth();
month = month + 1;
month = month.toString();
if (month.length < 2) {
month = '0' + month;
}
var year = pickerDate.getFullYear();
var hr = pickerDate.getHours();
var min = pickerDate.getMinutes();
if (hr < 10) {
hr = '0' + hr;
}
if (min < 10) {
min = '0' + min;
}
var datestr = '';
if (hr >= 12)
{
datestr += ' ' + (hr == 12 ? hr : hr - 12) + ':' + min + ' PM';
} else {
datestr += ' ' + hr + ':' + min + ' AM';
}
var newdate = month + "/" + day + "/" + year + datestr;
Titanium.API.info('converted value = ' + newdate);
tf1.setValue(newdate);
});
tview.add(datePicker);
tview.add(toolbar1);
winDatePicker1.add(tview);
winDatePicker1.show({
view : tf1,
animated : true
});
});
make setVisible(true) or setVisible(false)
show() and hide() will not give proper toggle at some conditions.