Detecta mouseover de ciertos puntos dentro de un canvas HTML?

Creé un motor de visualización de datos analíticos para Canvas y se me solicitó que agregara información emergente similar a la información sobre herramientas sobre los elementos de datos para mostrar métricas detalladas para el punto de datos debajo del cursor.

Para charts simples de barra y Gaant, charts de tree y maps de nodos con áreas cuadradas simples o puntos de interés específicos, pude implementar esto al superponer DIVs absolutamente posicionados con: attributes de desplazamiento, pero hay algunas visualizaciones más complicadas como charts circulares y una representación de flujo de tráfico que tiene cientos de áreas separadas definidas por curvas bezeir.

¿Es posible adjuntar de alguna manera una superposition, o desencadenar un evento cuando el usuario pasa sobre una ruta cerrada específica?

Cada área para la que se debe especificar el elemento emergente se define de la siguiente manera:

context.beginPath(); context.moveTo(segmentRight, prevTop); context.bezierCurveTo(segmentRight, prevTop, segmentLeft, thisTop, segmentLeft, thisTop); context.lineTo(segmentLeft, thisBottom); context.bezierCurveTo(segmentLeft, thisBottom, segmentRight, prevBottom, segmentRight, prevBottom); /* * ...define additional segments... */ // <dream> Ideally I would like to attach to events on each path: context.setMouseover(function(){/*Show hover content*/}); // </dream> context.closePath(); 

La vinculación a un object como este es casi trivial para implementar en Flash o Silverlight, ya que la implementación de Canvas actual tiene la ventaja de utilizar directamente nuestra API de Javascript existente y de integrarse con otros elementos de Ajax. Esperamos evitar include Flash en la mezcla.

¿Algunas ideas?

Podría manejar el evento mousemove y get las coorderadas x, y del evento. Entonces es probable que deba repetir todas sus routes para comprobar si el punto está sobre la ruta. Tuve un problema similar que podría tener algún código que podría usar.

Hacer loops sobre cosas de esta manera puede ser lento, especialmente en IE. Una forma en que podrías acelerarlo potencialmente -y esto es un truco, pero sería bastante efectivo- sería cambiar el color con el que se dibuja cada path para que no sea perceptible por los humanos, pero para que cada path se dibuje en un color diferente Tenga una tabla para search colors en las routes y simplemente busque el color del píxel debajo del mouse.

Shadow Canvas

El mejor método que he visto en otros lugares para la detección de mouseover es repetir la parte de tu dibujo que deseas detectar en un canvas oculto y despejado. A continuación, almacene el object ImageData. Luego puede verificar la matriz ImageData para el píxel de interés y devolver verdadero si el valor alfa es mayor que 0.

 // slow part ctx.clearRect(0,0,canvas.width,canvas.height); ctx.fillRect(100,100,canvas.width-100,canvas.height-100); var pixels = ctx.getImageData(0,0,canvas.width,canvas.height).data; // fast part var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3; if (pixels[idx]) { // alpha > 0 ... } 

Ventajas

  • Puedes detectar todo lo que quieras ya que solo estás repitiendo los methods de context. Esto funciona con PNG alpha, forms compuestas locas, text, etc.
  • Si su image es bastante estática, solo necesita hacer esto una vez por área de interés.
  • La "máscara" es lenta, pero search el píxel es muy barato. Entonces, la "parte rápida" es excelente para la detección de mouseover.

Desventajas

  • Este es un cerdo de la memory. Cada máscara es W * H * 4 valores. Si tiene un área de canvas pequeña o pocas áreas para enmascarar, no es tan malo. Usa el administrador de tareas de Chrome para monitorear el uso de la memory.
  • Actualmente hay un problema conocido con getImageData en Chrome y Firefox. Los resultados no son basura recogida de inmediato si anula la variable, por lo que si hace esto con demasiada frecuencia, verá que la memory aumenta rápidamente. Eventualmente obtiene basura recolectada y no debe colgar el browser, pero puede estar gravando en máquinas con pequeñas cantidades de RAM.

Un truco para salvar la memory

En lugar de almacenar toda la matriz ImageData, podemos recordar qué píxeles tienen valores alfa. Ahorra una gran cantidad de memory, pero agrega un bucle al process de máscara.

 var mask = {}; var len = pixels.length; for (var i=3;i<len;i+=4) if ( pixels[i] ) mask[i] = 1; // this works the same way as the other method var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3; if (mask[idx]) { ... } 

Esto podría hacerse utilizando el método ctx.isPointInPath, pero no está implementado en ExCanvas para IE. Pero otra solución sería usar maps HTML, como hice para esta pequeña biblioteca: http://phenxdesign.net/projects/phenx-web/graphics/example.htm puede get inspiración de ella, pero todavía es un poco calesa.

Sugiero superponer un map de image con las coorderadas adecuadas establecidas en las áreas para que coincidan con los elementos dibujados con canvas. De esta forma, obtienes información sobre herramientas Y una gran cantidad de otras funcionalidades DOM / Browser gratis.

Hay un libro de Eric Rowell llamado "HTML5 CANVAS COOKBOOK". En ese libro hay un capítulo llamado "Interactuando con el canvas: Adjuntando oyentes de events a forms y regiones". Se pueden implementar los events mousedown, mouseup, mouseover, mouseout, mousemove, touchstart, touchend y touchmove. Te sugiero que leas eso.

Esto no puede hacerse (bueno, al less no tan fácilmente), porque los objects que dibuja en el canvas (paths) no se representan como los mismos objects en el canvas. Lo que quiero decir es que es simplemente un context 2D simple y una vez que dibujas algo sobre él, olvida por completo cómo se dibujó. Es solo un set de píxeles para él.

Para ver mouseover y lo que le gusta, necesita algún tipo de canvas de charts vectoriales, es decir SVG o implementar uno propio sobre existente (que es lo que sugirió Sam Hasler)

Necesitaba detectar los clics del mouse para una cuadrícula de cuadrados (como las celdas de una spreadsheet de Excel). Para acelerarlo, dividí la cuadrícula en regiones recursivamente networkingucidas a la mitad hasta que quedó un pequeño número de celdas, por ejemplo, para una cuadrícula de 100×100, las primeras 4 regiones podrían ser las cuadrículas de 50×50 que comprenden los cuatro cuadrantes. Entonces estos podrían dividirse en otros 4 cada uno (dando por lo tanto 16 regiones de 25×25 cada una). Esto requiere un pequeño número de comparaciones y, finalmente, la cuadrícula de 25×25 podría probarse para cada celda (625 comparaciones en este ejemplo).