Render svg from svg.js inside pixi.js - pixi.js

Hi just testing to see if I can render an SVG created using svg.js inside a PIXI application. Codpen here
import * as PIXI from 'https://cdnjs.cloudflare.com/ajax/libs/pixi.js/6.5.4/browser/pixi.min.mjs'
const draw = SVG().addTo('body').attr({
viewBox: '0 0 100 100',
width: '100',
height: '100',
"enable-background":"new 0 0 64 64",
style: 'display: block'
})
draw.circle(100).x(50).fill('red')
console.log(draw.node)
const app = new PIXI.Application({
resolution: window.deviceAspectRatio || 1,
resizeTo: window
})
document.body.appendChild(app.view)
const {width,height} = app.renderer.view
console.log(width,height)
PIXI.Loader.shared.onProgress.add(loadProgressHandler)
PIXI.Loader.shared.add("svg",draw.node).load(setup)
function loadProgressHandler(){
console.log('loading')
}
function setup(){
console.log("setup")
const svgCanvas = new PIXI.Sprite(PIXI.Loader.shared.resources.svg.texture)
console.log(svgCanvas)
const container = new PIXI.Container()
container.addChild(svgCanvas)
app.stage.addChild(container)
}
Not getting any errors but also not rendering svg on canvas. Thanks in advance.

Related

d3 zoom event not firing for choropleth chart SVG created in storybook using nivo charts

I created a nivo choropleth chart in storybook and I need to add zoom functionality. I have a d3 zoom function which is supposed to listen for the zoom event on my svg. The zoom event doesn't seem to be firing.
The StyledResponsiveChoropleth renders as an SVG in the browser so I am using d3.select("svg") to access the chart.
When I zoom the component in the browser, nothing is happening, looks to me like the zoom event is not registering.
When I listen to mouse wheel events, they do show up.
This is my code below:
export const ChloropethChart: FC<IChoroplethChartProps> = ({
props
}) => {
// ***WHAT HAPPENS WHEN HANDLEZOOM IS CALLED????????????
const selectSVG = select<SVGSVGElement, unknown>('svg')
const rect = selectSVG
.append('rect')
.attr('width', '600px')
.attr('height', '600px')
.style('fill', 'none')
.style('pointer-events', 'all')
const handleZoom = (e: any) => {
selectSVG.append('g').attr('transform', e.transform)
}
const zoomBehaviour = zoom<SVGSVGElement, unknown>().on('zoom', handleZoom)
const initZoom = () => {
selectSVG.call(zoomBehaviour)
}
initZoom()
return (
<StyledChoroplethChart height={height}>
<StyledResponsiveChoropleth
data={data}
features={featureData}
margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
colors={colors ? colors : PURPLE_RANGE}
domain={domain}
/>
</StyledChoroplethChart>
)
}
export default ChloropethChart

Text in canvas being wiped out with a video canvas underneath

Note: Minimal working example here. Click on change text button, and you can see the text will appear for one frame and disappear.
I wanted to add an overlay to my video, so I stacked two canvases and filled text to the transparent top canvas.
However, the text does not stick. It disappears.
To test if I was filling the text correctly, I tried using a black canvas (no video) underneath.
I just needed to fillText once and the text stayed.
However, with the video canvas underneath, the text won't stick unless I draw text in requestAnimationFrame, which I believe keeps drawing the same text in every frame, which is unnecessary.
The text canvas is supposed to be separate from the video canvas. Why is this getting wiped out without requestAnimationFrame?
How can I fix it?
Minimal working example here, but the code is also shown below.
import "./styles.css";
import { useEffect, useRef, useState } from "react";
export default function App() {
const inputStreamRef = useRef();
const videoRef = useRef();
const canvasRef = useRef();
const overlayCanvasRef = useRef();
const [text, setText] = useState("Hello");
function drawTextWithBackground() {
const ctx = overlayCanvasRef.current.getContext("2d");
ctx.clearRect(
0,
0,
overlayCanvasRef.current.width,
overlayCanvasRef.current.height
);
/// lets save current state as we make a lot of changes
ctx.save();
/// set font
const font = "50px monospace";
ctx.font = font;
/// draw text from top - makes life easier at the moment
ctx.textBaseline = "top";
/// color for background
ctx.fillStyle = "#FFFFFF";
/// get width of text
const textWidth = ctx.measureText(text).width;
// Just note that this way of "measuring" height is not accurate.
// You can measure height of a font by using a temporary div / span element and
// get the calculated style from that when font and text is set for it.
const textHeight = parseInt(font, 10);
const textXPosition = canvasRef.current.width / 2 - textWidth / 2;
const textYPosition = canvasRef.current.height - textHeight * 3;
ctx.save();
ctx.globalAlpha = 0.4;
/// draw background rect assuming height of font
ctx.fillRect(textXPosition, textYPosition, textWidth, textHeight);
ctx.restore(); // this applies globalAlpha to just this?
/// text color
ctx.fillStyle = "#000";
ctx.fillText(text, textXPosition, textYPosition);
/// restore original state
ctx.restore();
}
function updateCanvas() {
const ctx = canvasRef.current.getContext("2d");
ctx.drawImage(
videoRef.current,
0,
0,
videoRef.current.videoWidth,
videoRef.current.videoHeight
);
// QUESTION: Now the text won't stick unless I uncomment this below,
// which runs drawTextWithBackground in requestAnimationFrame.
// Previosuly, with just a black canvas underneath, I just needed to fillText once
// and the text stayed.
// The text canvas is supposed to be separate. Why is this getting wiped out without requestAnimationFrame?
// drawTextWithBackground();
requestAnimationFrame(updateCanvas);
}
const CAMERA_CONSTRAINTS = {
audio: true,
video: {
// the best (4k) resolution from camera
width: 4096,
height: 2160
}
};
const enableCamera = async () => {
inputStreamRef.current = await navigator.mediaDevices.getUserMedia(
CAMERA_CONSTRAINTS
);
videoRef.current.srcObject = inputStreamRef.current;
await videoRef.current.play();
// We need to set the canvas height/width to match the video element.
canvasRef.current.height = videoRef.current.videoHeight;
canvasRef.current.width = videoRef.current.videoWidth;
overlayCanvasRef.current.height = videoRef.current.videoHeight;
overlayCanvasRef.current.width = videoRef.current.videoWidth;
requestAnimationFrame(updateCanvas);
};
useEffect(() => {
enableCamera();
});
return (
<div className="App">
<video
ref={videoRef}
style={{ visibility: "hidden", position: "absolute" }}
/>
<div style={{ position: "relative", width: "90vw" }}>
<canvas
style={{ width: "100%", top: 0, left: 0, position: "static" }}
ref={canvasRef}
width="500"
height="400"
/>
<canvas
style={{
width: "100%",
top: 0,
left: 0,
position: "absolute"
}}
ref={overlayCanvasRef}
width="500"
height="400"
/>
</div>
<button
onClick={() => {
setText(text + "c");
drawTextWithBackground();
}}
>
change text
</button>
</div>
);
}
Very simple fix
The canvas is being cleared due to how React updates state.
The fix
The simplest fix is to use just the one canvas and draw the text onto the canvas used to display the video.
From the working example change the two functions updateCanvas and drawTextWithBackground as follows.
Note I have removed all the state saving in drawTextWithBackground as the canvas state is lost due to react anyway so why use the very expensive state stack functions.
function drawTextWithBackground(ctx) {
const can = ctx.canvas;
const textH = 50;
ctx.font = textH + "px monospace";
ctx.textBaseline = "top";
ctx.textAlign = "center";
ctx.fillStyle = "#FFFFFF66"; // alpha 0.4 as part of fillstyle
ctx.fillStyle = "#000";
const [W, X, Y] = [ctx.measureText(text).width, can.width, can.height - textH * 3];
ctx.fillRect(X - W / 2, Y, W, textH);
ctx.fillText(text, X, Y);
}
function updateCanvas() {
const vid = videoRef.current;
const ctx = canvasRef.current.getContext("2d");
ctx.drawImage(vid, 0, 0, vid.videoWidth, vid.videoHeight);
drawTextWithBackground(ctx);
requestAnimationFrame(updateCanvas);
}
when you update the canvas from the video, all of the pixels in the canvas are updated with pixels from the video. Think of it like a wall:
you painted "here's my text" on the wall.
Then you painted the whole wall blue. (your text is gone, and the wall is just blue).
If you paint the whole wall blue, and then repaint "here's my text", you'd still see your text on the blue wall.
When you redraw the canvas, you have to re-draw everything you want to appear on the canvas. If the video "covers" your text, you've got to redraw the text.
working example at https://github.com/dougsillars/chyronavideo which draws a chyron (the bar at the bottom of a news story) on each frame of the canvas.

How to install Google Font on nodejs-canvas?

I'm trying to get Google Fonts dynamically with nodejs-canvas and I'm not sure what is the best way to do that.
I understand it needs to be with: "registerFont" method.
This is the code I have now:
const { registerFont, createCanvas, loadImage } = require('canvas')
//registerFont('comicsans.ttf', { family: 'Comic Sans' })
const canvas = createCanvas(200, 200)
const ctx = canvas.getContext('2d')
let name = req.query.name;
// Write "Awesome!"
ctx.font = '30px Impact'
ctx.rotate(0.1)
ctx.fillText(name, 50, 100)
// Draw line under text
var text = ctx.measureText(name)
ctx.strokeStyle = 'rgba(0,0,0,0.5)'
ctx.beginPath()
ctx.lineTo(50, 102)
ctx.lineTo(50 + text.width, 102)
ctx.stroke()
//console.log(canvas.toDataURL());
res.write('<img style="border:1px solid #000;" src="' + canvas.toDataURL() + '" />');
res.end();
Using PT Mono as an example this is how you do it.
First go to the font directly in this case PT Mono.
Next click the "download family" link in the top right. That will give you a zip file which contains the TTF font.
Now copy the ttf file into your node application directory and use registerFont in your file like this.
registerFont('PtMono-Regular.ttf', { family: 'PT Mono' });
As per the documentation make sure you register your fonts before creating the canvas.
Then to use your font on your canvas you'd just use the following to set the font for the context.
ctx.font = "16px 'PT Mono'";
Full Example using our google font just modifying the original example in the node-canvs repo.
const { registerFont, createCanvas } = require('canvas');
registerFont('PtMono-Regular.ttf', { family: 'PT Mono' });
const canvas = createCanvas(500, 500);
const ctx = canvas.getContext('2d');
ctx.font = '16px 'PT Mono'"'
ctx.fillText('This is the font.', 250, 10);

Polygon rendering with pixijs

I'm trying to display more than 6000 Polygons on a mobile device.
Currently, I'm doing this with SVG paths in Android WebView using the d3.js library.
It works but I have to deal with performance issues, my map becomes very laggy when I drag my map or zoom.
My Idea now is to try the same with pixijs. My data comes originally from ESRI Shapefiles. I'm convert these Shapefiles to GeoJSON and then to SVG. My array of vertices looks like this, which I'm trying to pass to the drawPolygon function
0: 994.9867684400124
1: 22.308409862458518
2: 1042.2789743912592
3: 61.07148769269074
But when I try to render these polygon nothing being displayed. This is my code:
var renderer = PIXI.autoDetectRenderer(1800, 1800, { backgroundColor: 0x000000, antialias: true });
document.body.appendChild(renderer.view);
var stage = new PIXI.Container();
var graphics = new PIXI.Graphics();
var totalShapes = feat.features.length;
for (var i = 1; i <= totalShapes -1; i++) {
var shape = feat.features[i];
var geometry = shape.geometry.bbox;
graphics.beginFill(0xe74c3c);
graphics.drawPolygon([ geometry]);
graphics.endFill();
stage.addChild(graphics);
renderer.render(stage);
}
Can someone help me or could suggest me a different way?
I have not seen that way of initializing a pixie project.
Usually you add the application to the html document like:
var app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: 0x2c3e50
});
document.body.appendChild(app.view);
If you do this you can add your draw calls to the setup of the application:
app.loader.load(startup);
function startup()
{
var g = new PIXI.Graphics();
g.beginFill(0x5d0015);
g.drawPolygon(
10, 10, 120, 100, 120, 200, 70, 200
);
g.endFill();
app.stage.addChild(g);
}
This will render the polygon once.

How to render a circle in pixijs

i'm banging my head to wall. I cannot render simple circle in pixijs. Any healp appreciated?
Playgroud link - https://pixiplayground.com/#/edit/eE~VcTxQSEi_I_DZk6Agz
If you are just trying to render a circle, this is all you need:
var app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: 0x2c3e50
});
document.body.appendChild(app.view);
const gr = new PIXI.Graphics();
gr.beginFill(0xffffff);
gr.drawCircle(30, 30, 30);
gr.endFill();
app.stage.addChild(gr)
Here is a working example: https://www.pixiplayground.com/#/edit/5_EeuD2_YmdV8oleXRZ2P

Resources