Is there a way to export a widget to png with an arbitrary scale in Kivy?
As far as I understand, now by default the scale of an exported image is 1:1 relative to size of widget that I see on the screen.
But what if I want to export to 1:10 (and get an image 10 times larger)?
Interested in any ideas how this can be done.
I don't know a straight forward method to do the same, but you can fork export_to_png method of Widget class
Here is a working example:
from kivy.uix.widget import Widget
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.graphics import (Canvas, Translate, Fbo, ClearColor,
ClearBuffers, Scale)
kv='''
BoxLayout:
orientation: 'vertical'
MyWidget:
id: wgt
BoxLayout:
size_hint_y: .1
orientation: 'horizontal'
Label:
text: 'enter scale:'
TextInput:
id: txtinpt
text: '2.5'
Button:
text: 'Export with new scale'
on_release: wgt.export_scaled_png('kvwgt.png', image_scale=float(txtinpt.text))
<MyWidget>:
canvas:
PushMatrix:
Color:
rgba: (0, 0, 1, .75)
Ellipse:
pos: (self.x + self.width // 5, self.y + self.height // 5)
size: (self.width * 3 // 5, self.height * 3 // 5)
Color:
rgba: (1, 0, 0, .5)
Rectangle:
pos: (self.x + self.width // 4, self.y + self.height // 4)
size: (self.width // 2, self.height // 2)
Rotate:
origin:
self.center
angle:
45
Button:
text: 'useless child widget\\njust for demonstration'
center: root.center
size: (root.width // 2, root.height // 8)
canvas:
PopMatrix:
'''
class MyWidget(Widget):
def export_scaled_png(self, filename, image_scale=1):
re_size = (self.width * image_scale,
self.height * image_scale)
if self.parent is not None:
canvas_parent_index = self.parent.canvas.indexof(self.canvas)
if canvas_parent_index > -1:
self.parent.canvas.remove(self.canvas)
fbo = Fbo(size=re_size, with_stencilbuffer=True)
with fbo:
ClearColor(0, 0, 0, 0)
ClearBuffers()
Scale(image_scale, -image_scale, image_scale)
Translate(-self.x, -self.y - self.height, 0)
fbo.add(self.canvas)
fbo.draw()
fbo.texture.save(filename, flipped=False)
fbo.remove(self.canvas)
if self.parent is not None and canvas_parent_index > -1:
self.parent.canvas.insert(canvas_parent_index, self.canvas)
runTouchApp(Builder.load_string(kv))
Note re_size variable, which in turn passes to Fbo constructor.
And the "Scale(image_scale, -image_scale, image_scale)" instruction inside export_scaled_png method.
Related
I'm working with React and ChartJS to draw a doughnut chart with a 3*Pi/2 circumference
and rounded corner.
I saw these two posts where they explain how to round corners for data sets and it is working as expected with a complete circle and with half a circle:
ChartJs - Round borders on a doughnut chart with multiple datasets
Chartjs doughnut chart rounded corners for half doghnut
One answer on this post is to change "y" or "x" translation by factor of n, for example 2 in the following case: ctx.translate(arc.round.x, arc.round.y*2);
With this in mind I started to change values for x and y but have not yet reach the correct set of values that will make it work.
For example I tried to use a factor of 3/2 on the translation of y and this is what I get.
ctx.translate(arc.round.x, (arc.round.y * 3) / 2);
with no factor I get the following:
ctx.translate(arc.round.x, arc.round.y);
The code to round the end corner is exactly the same as in the posts I refer. But here it is just in case:
let roundedEnd = {
// #ts-ignore
afterUpdate: function (chart) {
var a = chart.config.data.datasets.length - 1;
for (let i in chart.config.data.datasets) {
for (
var j = chart.config.data.datasets[i].data.length - 1;
j >= 0;
--j
) {
if (Number(j) == chart.config.data.datasets[i].data.length - 1)
continue;
var arc = chart.getDatasetMeta(i).data[j];
arc.round = {
x: (chart.chartArea.left + chart.chartArea.right) / 2,
y: (chart.chartArea.top + chart.chartArea.bottom) / 2,
radius:
chart.innerRadius +
chart.radiusLength / 2 +
a * chart.radiusLength,
thickness: (chart.radiusLength / 2 - 1) * 2.5,
backgroundColor: arc._model.backgroundColor,
};
}
a--;
}
},
// #ts-ignore
afterDraw: function (chart) {
var ctx = chart.chart.ctx;
for (let i in chart.config.data.datasets) {
for (
var j = chart.config.data.datasets[i].data.length - 1;
j >= 0;
--j
) {
if (Number(j) == chart.config.data.datasets[i].data.length - 1)
continue;
var arc = chart.getDatasetMeta(i).data[j];
var startAngle = Math.PI / 2 - arc._view.startAngle;
var endAngle = Math.PI / 2 - arc._view.endAngle;
ctx.save();
ctx.translate(arc.round.x, arc.round.y);
console.log(arc.round.startAngle);
ctx.fillStyle = arc.round.backgroundColor;
ctx.beginPath();
//ctx.arc(arc.round.radius * Math.sin(startAngle), arc.round.radius * Math.cos(startAngle), arc.round.thickness, 0, 2 * Math.PI);
ctx.arc(
arc.round.radius * Math.sin(endAngle),
arc.round.radius * Math.cos(endAngle),
arc.round.thickness,
0,
2 * Math.PI
);
ctx.closePath();
ctx.fill();
ctx.restore();
}
}
}, };
These are the options to configure the chart:
const chartJsOptions = useMemo<chartjs.ChartOptions>(() => {
if (data) {
return {
elements: {
center: {
text: `${data.impact > 0 ? "%"}`,
color: isDarkTheme ? darkText : greyAxis, // Default is #000000
fontStyle: "Open Sans Hebrew, sans-serif",
sidePadding: 20, // Default is 20 (as a percentage)
minFontSize: 15, // Default is 20 (in px), set to false and text will not wrap.
lineHeight: 20, // Default is 25 (in px), used for when text wraps
},
},
legend: {
display: false,
},
// rotation: Math.PI / 2,
rotation: (3 * Math.PI) / 4,
circumference: (3 * Math.PI) / 2,
maintainAspectRatio: false,
animation: {
duration: ANIMATION_DURATION,
},
plugins: {
datalabels: false,
labels: false,
},
cutoutPercentage: 90,
tooltips: {
enabled: false,
rtl: true,
},
};
} else {
return {};
} }, [data, isDarkTheme]);
Here is where I call the react component for the chart:
<Doughnut
data={chartJsData}
options={chartJsOptions}
plugins={[roundedEnd]} />
How can I correctly calculate the rounded edges on a 3*Pi/2 circumference or any other circumference between complete and half?
This issue may be more of a math than programing and my geometrical math is also a bit rusty.
I'd like to animate/change colour based on the current section of an oscillator wave, to create a pulsing colour effect that matches the wave? The waves can be any of the types e.g. sine, triangle etc. so this would create different pulsing depending on the wave type and the frequency can be changed for the oscillator too (which I would then like to also change the pulsing timings).
Presumably I need to get the amplitude and wavelength (are these even the right terms for what i need?) from the oscillator object and use it in correlation with the Draw object while using some css animation but I am a bit stuck as to where to start? Specifically around the tone.js part of how I would get the values needed and how I would morph from colour A to B and back etc over time. Would I need to include an external library like p5 or can I do this all through tone.js with draw alone?
cheers
david
EDIT - #paulwheeler - Thanks so much for all your comments, that is a big help. To be clearer, and to answer your queries, here is what I have and need, should you have any more input -
i) I have two oscillators continuously playing from Tone.js. They are 'playing' at frequency A and B, to create a binaural sound. The difference between the two i will call the range. So for example, Osc1 might be 100hz, osc2 might be 104hz and the range is 4hz. These oscillators can be of any type allowed by tone.js (sawtooth, sine, triangle..)
ii) When the sound is playing i want to take the range freqency and attach it to two colours. So at the peak of the frequency colour A will be showing as a background colour and at the trough of the frequency colour B will be showing. Inbetween those times the colour will be morphing between the two i.e. at any time t the colour will be a representation of the Y position on the wave, that is reflective of the distance from 0. This will then make it appear that the colour is changing to match the shape of the waves. kind of like here in css (but just using keyframes alone in this example and just to give an example of what i mean visually) -
https://jsfiddle.net/CF3Np/4/
#keyframes animation {
0% {background-color:red;}
50.0% {background-color:green;}
100.0% {background-color:red;}
}
From a cursory scan of the tone.js documentation it looks like it has everything you could want to synthesize and analyze sounds. However it doesn't appear have any facility for graphics. As far as drawing graphics goes, p5.js is certainly one option. However, the reference to "css animation" is a bit out of place here, because libraries like p5.js aren't really designed with css styling in mind. Instead you would be doing all of the animation yourself with calls to drawing functions for each frame.
The thing you are looking for when it comes to analyzing sounds in order to process them as data, and potentially visualize them, is a Fast Fourier Transform. This is an algorithm that decomposes a sample of a sound wave into separate sine wave components, and you can use the amplitudes of those sine waves to measure the sound in terms of component frequencies. Both p5.js (via the p5.sound add-in) and tone.js have FFT classes. If you're already using tone.js you'll probably want to stick with that for analyzing sounds, but you can certainly create visualizations for them using p5.js. Just as a conceptual example, here's a pure p5.js example:
const notes = [{
name: 'c',
freq: 261.6
},
{
name: 'c#',
freq: 277.2,
sharp: true
},
{
name: 'd',
freq: 293.7
},
{
name: 'd#',
freq: 311.1,
sharp: true
},
{
name: 'e',
freq: 329.6
},
{
name: 'f',
freq: 349.2
},
{
name: 'f#',
freq: 370.0,
sharp: true
},
{
name: 'g',
freq: 392.0
},
{
name: 'g#',
freq: 415.3,
sharp: true
},
{
name: 'a',
freq: 440.0
},
{
name: 'a#',
freq: 466.2,
sharp: true
},
{
name: 'b',
freq: 493.9
},
];
let playing = {};
let fft;
let bg;
function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(HSB);
bg = color('lightgray');
for (let note of notes) {
note.osc = new p5.Oscillator();
note.osc.freq(note.freq);
note.osc.amp(0);
}
fft = new p5.FFT();
}
function toggleNote(name) {
let note = notes.filter(n => n.name === name)[0];
if (playing[name] === undefined) {
// First play
note.osc.start();
}
if (playing[name] = !playing[name]) {
// fade in a little
note.osc.amp(0.2, 0.2);
} else {
// fade out a little
note.osc.amp(0, 0.4);
}
}
function playNote(name) {
let note = notes.filter(n => n.name === name)[0];
if (playing[name] === undefined) {
// First play
note.osc.start();
}
playing[name] = true;
note.osc.amp(0.2, 0.2);
}
function releaseNote(name) {
let note = notes.filter(n => n.name === name)[0];
playing[name] = false;
note.osc.amp(0, 0.4);
}
function draw() {
background(bg);
let w = width / 3;
let h = min(height, w * 0.8);
drawSpectrumGraph(w, 0, w, h);
drawWaveformGraph(w * 2, 0, w, h);
drawKeyboard();
bg = color((fft.getCentroid() * 1.379) % 360, 30, 50);
}
function drawSpectrumGraph(left, top, w, h) {
let spectrum = fft.analyze();
stroke('limegreen');
fill('darkgreen');
strokeWeight(1);
beginShape();
vertex(left, top + h);
for (let i = 0; i < spectrum.length; i++) {
vertex(
left + map(log(i), 0, log(spectrum.length), 0, w),
top + map(spectrum[i], 0, 255, h, 0)
);
}
vertex(left + w, top + h);
endShape(CLOSE);
}
function drawWaveformGraph(left, top, w, h) {
let waveform = fft.waveform();
stroke('limegreen');
noFill();
strokeWeight(1);
beginShape();
for (let i = 0; i < waveform.length; i++) {
let x = map(i * 5, 0, waveform.length, 0, w);
let y = map(waveform[i], -1, 2, h / 10 * 8, 0);
vertex(left + x, top + y);
}
endShape();
}
function drawKeyboard() {
let w = width / 3;
let h = min(height, w * 0.8);
let x = 1;
let keyWidth = (w - 8) / 7;
let sharpWidth = keyWidth * 0.8;
noStroke();
let sharpKeys = [];
for (let note of notes) {
fill(playing[note.name] ? 'beige' : 'ivory');
if (note.sharp) {
sharpKeys.push({
fill: playing[note.name] ? 'black' : 'dimgray',
rect: [x - sharpWidth / 2, 0, sharpWidth, h / 2, 0, 0, 4, 4]
});
} else {
rect(x, 0, keyWidth, h - 1, 0, 0, 4, 4);
x += keyWidth + 1;
}
}
for (let key of sharpKeys) {
fill(key.fill);
rect(...key.rect);
}
}
let keymap = {
'z': 'c',
's': 'c#',
'x': 'd',
'd': 'd#',
'c': 'e',
'v': 'f',
'g': 'f#',
'b': 'g',
'h': 'g#',
'n': 'a',
'j': 'a#',
'm': 'b',
}
function keyPressed(e) {
let note = keymap[e.key];
if (note) {
playNote(note);
}
}
function keyReleased(e) {
let note = keymap[e.key];
if (note) {
releaseNote(note);
}
}
function mouseClicked() {
if (mouseX < width / 3) {
let w = width / 3;
let h = w * 0.8;
let x = 1;
let keyWidth = (w - 8) / 7;
let sharpWidth = keyWidth * 0.8;
let naturalKeys = [];
let sharpKeys = [];
for (let note of notes) {
if (note.sharp) {
sharpKeys.push({
name: note.name,
bounds: {
left: x - sharpWidth / 2,
top: 0,
right: x - sharpWidth / 2 + sharpWidth,
bottom: h / 2
}
});
} else {
naturalKeys.push({
name: note.name,
bounds: {
left: x,
top: 0,
right: x + keyWidth,
bottom: h - 1
}
});
x += keyWidth + 1;
}
}
for (let {
bounds,
name
} of sharpKeys.concat(naturalKeys)) {
if (mouseX > bounds.left && mouseX < bounds.right &&
mouseY > bounds.top && mouseY < bounds.bottom) {
toggleNote(name);
break;
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/addons/p5.sound.min.js"></script>
I use react Grid-layout : https://github.com/react-grid-layout/react-grid-layout
I do not understand why the number of column does not update in my table through the web browser.
In fact, this.props.cols.lg is either 17 or 24.
When it's 17, my table is displayed correctly. On the other hand, when I choose a period with 24 columns, my columns are placed one below the other from the 18th.
this.props.cols.lg to the correct value (17 or 24) but my HTML page does not update to the design level.
This is the right table on one single row :
Right table
Like you can see, the firefox web console shows react properties are good with 17 :
and the bad table
Bad table
here again, the firefox web console shows react properties are right (24) but not applied by the web browser :
Important information,
this.props.cols.lg=loopValue+1; **contains the right value (17 or 24) but apply on 17**
Thank you in advance for your help.
Here is a part of my code:
import React, { useState, useEffect, Fragment } from 'react';
import { WidthProvider, Responsive } from "react-grid-layout";
import ReactTooltip from 'react-tooltip';
import _ from "lodash";
import { Button, Container } from 'react-bootstrap';
const ResponsiveReactGridLayout = WidthProvider(Responsive);
import { Responsive as ResponsiveGridLayout } from 'react-grid-layout';
/**
* This layout demonstrates how to use a grid with a dynamic number of elements.
*/
export class AddRemoveLayout extends React.PureComponent {
//les propriétés par défaut
static defaultProps = {
className: "layout border-darken-1 child-background-color",
cols:
{ lg: 10, md: 10, sm: 10, xs: 10, xxs: 10 },
width: 1000,
margin: [0, 0],
preventCollision: true,
autoSize: true,
// preventCollision: true,
rowHeight: 70,
// Build HTML to insert
render() {
//Week
let createDateItem =
(x, y, day) => ({
i: `date-${x}_${y}`,
x,
y,
w: 1,
h: 1,
myText: `${day}` ,
static: true
});
/**
*
* #param {*} y
* #param {*} xStart
* #param {num semaine} dateStart
* #param {nombre semaine total} count
*/
let getDateItems =
(y, xStart, dateStart, count,dateEnd) => {
let items = [];
let loopValue = 0;
while(dateStart<=dateEnd){//semainee
if(loopValue>1){
dateStart.setDate(dateStart.getDate() + 7)
}
if(dateStart<=dateEnd){
items.push(
createDateItem(xStart + loopValue, y, loopValue === 0 ? 0 :
(dateStart.getDate()+"/" +(dateStart.getUTCMonth()+1)+ " (Sem. "+loopValue +") ")
))
}
loopValue++;
}
console.log('props:')
console.log(this.props);
this.props.cols.lg=loopValue+1;
this.props.cols.md=loopValue+1;
this.props.cols.sm=loopValue+1;
this.props.cols.xs=loopValue+1;
this.props.cols.xxsloopValue+1;
console.log(this.props);
// console.log(AddRemoveLayout.defaultProps)
return items;
}
;
We found the solution :
data: { idHoraire: 1 },
It was always the same item sent to PHP, with this code, I have a great dispaly of my cols because I either send 1 (17 cols) or 2 (25 cols) :
data: { idHoraire: url === "index2" ?2 : 1, },
I'm building a react native app where the user should be able to move an adjuster around a pie chart to adjust the start and end angles of the pie slices. I'm using 3 panResponders and used the idea by /users/681830/val:
react native circle transform translate animation
I'm calculating the shortest distance to either of the 36 snapshots then set the animated value to that point however it is super inefficient and laggy. Can anyone suggest any better performing solution?
'''
this._panResponder1 = PanResponder.create(
{
onStartShouldSetPanResponder: (evt, gesture) =>true,
onPanResponderMove: (evt, gesture) => {
//we need the distance between the points and get the index of the minimum distance
distances = [];
for(var i = 0; i < 36; i++){
var a = this.outputRangeX[i] - gesture.moveX;
var b = this.outputRangeY[i] - gesture.moveY + 120;
distances.push(Math.sqrt(a*a + b*b));
}
var minInd = distances.indexOf(Math.min(...distances));
this.setState({indexOfAdj1 : minInd});
this.adj1Anim.setValue((1/36)* minInd);
var isPos1 = minInd/36;
var isPos2 = (minInd)/36;
if(minInd>24){
isPos1 = -1 * ((36-minInd)/36);
isPos2 = minInd/36;
this.setState({data: [
{
number: 1,
startAngle: isPos1* Math.PI * 2,
endAngle: this.state.data[0].endAngle,
},
{
number: 30,
startAngle: this.state.data[1].startAngle,
endAngle: this.state.data[1].endAngle,
},
{
number: 1,
startAngle: this.state.data[1].endAngle,
endAngle: isPos2* Math.PI * 2,
},
]});
}else{
this.setState({data: [
{
number: 1,
startAngle: isPos1* Math.PI * 2,
endAngle: this.state.data[0].endAngle,
},
{
number: 30,
startAngle: this.state.data[1].startAngle,
endAngle: this.state.data[1].endAngle,
},
{
number: 1,
startAngle: -((Math.PI * 2)-this.state.data[1].endAngle),
endAngle: isPos2* Math.PI * 2,
},
]});
}
}
'''
I want to place title in center of pie chart. I have a method call addTitle that it place title in center of pie chart. This method is executed by the load event. My code is the next:
addTitle = (char) ->
console.log 'entra', char
if char.title
char.title.destroy()
r = char.renderer
x = char.series[0].center[0] + char.plotLeft //Series is undefined, Why?
y = char.series[0].center[1] + char.plotTop //Series is undefined, Why?
char.title = r.text('Series 1', 0, 0)
.css({
color: '#4572A7',
fontSize: '16px'
}).hide()
.add();
bbox = char.title.getBBox();
char.title.attr({
x: x - (bbox.width / 2),
y: y
}).show();
events: {
load: () ->
addTitle(this)
}