I'm creating a widget in PySide/PyQt that adds circles to a line.
All of these elements are painted using QPainter in the paintevent method. However, I'm currently trying to turn those circles into buttons. How do I use the paintEvent function to "draw" a button instead of a pixmap, or make a pixmap clickable ?
def paintEvent(self, e):
painter = QtGui.QPainter(self)
margin = 50
begin_x = margin
begin_y = 0
begin_circle = QPixmap('images\icons8-circle-50.png')
painter.drawPixmap(begin_x/2, begin_y,begin_circle.width()/2,begin_circle.height()/2,begin_circle)
end_circle = begin_circle
end_x = painter.device().width()-margin
end_y = begin_y
painter.drawPixmap(end_x, end_y,end_circle.width()/2,end_circle.height()/2,end_circle)
brush = QtGui.QBrush()
brush.setColor(QtGui.QColor('black'))
brush.setStyle(Qt.SolidPattern)
self.begin_rect = (begin_circle.width()/2.2)+begin_x/2
self.width_rect = ((painter.device().width()-margin)-end_circle.width()+5)
rect = QtCore.QRect(self.begin_rect ,begin_circle.height()/4.7,self.width_rect , 8)
painter.fillRect(rect, brush)
painter.drawText(begin_x/2, begin_y+45, "00:00")
painter.drawText(end_x, begin_y+45, "23:59")
for time in self.list_of_timestamps:
time_circle = QPixmap('images\icons8-100--60.png')
time_x = self._get_time_line_translated(time)
time_y = begin_y
painter.drawPixmap(time_x, time_y,time_circle.width()/2,time_circle.height()/2,time_circle)
painter.end()
The for loop goes through the timestamps and prints a pixmap for each one, my question is how do i do this with buttons instead of pixmaps?
For the record, this is how the app currently looks like
Thanks in advance
Related
I have two QRects lined side by side:
def paintEvent(self, event):
painter = QPainter(self)
self.rect1 = QRect(0, 0, 500, 40)
painter.fillRect(self.rect1, Qt.GlobalColor.black)
self.rect2 = QRect(500, 0, 500, 40)
painter.fillRect(self.rect2, Qt.GlobalColor.white)
If user clicks somewhere in second QRect, I want to know the location of the click relative to the (0, 0) of the QRect:
def mousePressEvent(self, event):
pos = event.pos()
if self.rect2.contains(pos):
relative_pos = QPoint(pos.x() - self.rect2.x(), pos.y() - self.rect2.y())
Is there a built-in way to do this?
I'm trying to draw three separate round rectangles and then fill all three with a linear gradient, but have the gradient traverse all three objects. I can't seem to find any information on drawing objects onto a canvas and linking them together.
I'm very new at using canvas in general.
Here is what I have so far:
final Canvas canvas = new Canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
final GraphicsContext gc = canvas.getGraphicsContext2D();
double offset1 = .1;
double offset2 = .9;
Color color1 = Color.rgb(0,0,255);
Color color2 = Color.rgb(0,200,255);
Stop[] stops1 = new Stop[] {new Stop(offset1, color1), new Stop(offset2, color2)};
gc.setFill(new LinearGradient(0, 0, .2, 1.4, true, CycleMethod.NO_CYCLE, stops1));
double third = CANVAS_HEIGHT / 3.0;
double thirdM2 = third - 10;
double h2 = CANVAS_HEIGHT - (2 * third);
double h3 = CANVAS_HEIGHT - third;
gc.fillRoundRect(0,0,CANVAS_WIDTH,thirdM2,thirdM2,thirdM2);
gc.fillRoundRect(0,h2,CANVAS_WIDTH,thirdM2,thirdM2,thirdM2);
gc.fillRoundRect(0,h3,CANVAS_WIDTH,thirdM2,thirdM2,thirdM2);
This makes an image that looks like this
However, I need it to end up looking like this:
Is there any way to accomplish this in JavaFX with canvas?
Thank you,
Mike Sims
Yes. There are a couple ways to do this. You could render all three rounded rectangles as a single path (using path rendering instead of fillRoundedRect, e.g. moveTo, arcTo etc.), or you could change the render blend mode to SRC_ATOP and paint a rectangle over all three rounded rects. That's what I've done in the code below:
final Canvas canvas = new Canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
final GraphicsContext gc = canvas.getGraphicsContext2D();
double offset1 = .5;
double offset2 = .9;
Color color1 = Color.rgb(0,0,255);
Color color2 = Color.rgb(0,200,255);
Stop[] stops1 = new Stop[] {new Stop(offset1, color1), new Stop(offset2, color2)};
double third = CANVAS_HEIGHT / 3.0;
double thirdM2 = third - 10;
double h2 = CANVAS_HEIGHT - (2 * third);
double h3 = CANVAS_HEIGHT - third;
gc.fillRoundRect(0,0,CANVAS_WIDTH,thirdM2,thirdM2,thirdM2);
gc.fillRoundRect(0,h2,CANVAS_WIDTH,thirdM2,thirdM2,thirdM2);
gc.fillRoundRect(0,h3,CANVAS_WIDTH,thirdM2,thirdM2,thirdM2);
gc.setGlobalBlendMode(BlendMode.SRC_ATOP); // preserves the alpha channel set above
gc.setFill(new LinearGradient(0, 0, .6, .5, true, CycleMethod.NO_CYCLE, stops1));
gc.fillRect(0, 0, CANVAS_WIDTH, h3+thirdM2);
If you don't want to muck around directly on the canvas (because there may be other elements that get in the way that you can't paint over), you can do this trick to a writeable image and then stamp that onto the canvas.
I'm using DisplayMetrics and FontMetrics to compute a value for TextSize so that the text inside the Button is not bigger that the button bounding box.
DisplayMetrics displayMetrics = new DisplayMetrics();
Display.GetMetrics(displayMetrics);
float scaledDensity = displayMetrics.ScaledDensity;
TextPaint paint = new TextPaint();
paint.AntiAlias = true;
Paint.FontMetrics metrics = null;
Xamarin.Forms.Button button;
MyButtonRenderer renderer = ((MyButtonRenderer)Platform.GetRenderer(button));
Android.Widget.Button control = renderer.Control;
int maxFontSize = (int)renderer.defaultFontSize;
string text = button.Text;
Rect bounds = new Rect();
while (maxFontSize >= 0)
{
paint.SetTypeface(control.Typeface);
paint.TextSize = scaledDensity * maxFontSize;
paint.GetTextBounds(text, 0, text.Length, bounds);
metrics = paint.GetFontMetrics();
if (bounds.Width() < control.MeasuredWidth && (metrics.Bottom - metrics.Top) < control.MeasuredHeight)
{
break;
}
maxFontSize--;
}
Then I thought that using maxFontSize to set the property TextSize for the button would make the text of the appropriate size:
renderer.Control.TextSize = maxFontSize * scaledDensity;
It doesn't work though, what I had to do was this instead:
float conversionScale = (float)renderer.Element.FontSize / renderer.Control.TextSize;
float maxTextSize = maxFontSize * scaledDensity;
renderer.Element.FontSize = maxTextSize * conversionScale;
Where conversionScale is what I use to covert TextSize to FontSize.
Is there a way to get this conversion factor from the API instead of inferring its value from the quotient of the two properties?
Also asked at the official forums.
I am using orson chart's Chart3D to make a surface plot, and for some reason the graph isn't properly coloring the gradient. The code is below:
#SuppressWarnings("serial")
Function3D function = new Function3D() {
#Override
public double getValue(double x, double z) {
double xKey = Math.round(x * 100) / 100;
double zKey = Math.round(z * 100) / 100;
if(plotValues.containsKey(new Point2D(xKey,zKey))) {
return plotValues.get(new Point2D(xKey,zKey));
} else {
return 0;
}
}
};
String xTitle = factorSweepComboBox.getSelectionModel().getSelectedItem();
String yTitle = outputComboBox.getSelectionModel().getSelectedItem();
String zTitle = factorSweep2ComboBox.getSelectionModel().getSelectedItem();
// Create surface plot
Chart3D chart = Chart3DFactory.createSurfaceChart(
"",
"",
function, xTitle, yTitle, zTitle);
XYZPlot xyzplot = (XYZPlot) chart.getPlot();
xyzplot.setDimensions(new Dimension3D(10, 10, 10));
ValueAxis3D xAxis = xyzplot.getXAxis();
xAxis.setRange(xLow, xUp);
ValueAxis3D zAxis = xyzplot.getZAxis();
zAxis.setRange(zLow, zUp);
ValueAxis3D yAxis = xyzplot.getYAxis();
yAxis.setRange(yLow, yUp);
SurfaceRenderer renderer = (SurfaceRenderer) xyzplot.getRenderer();
renderer.setColorScale(new GradientColorScale(new Range(yLow, yUp),
Color.BLUE, Color.YELLOW));
Chart3DViewer chartPanel = new Chart3DViewer(chart);
chartPane.getChildren().addAll(chartPanel);
plotValues is a hashmap mapping a (x,z) 2D point to a double y output value. xLow, xUp, etc. are range values and are all being set correctly. yLow and yUp are what I want them to be. However, when I run the code my surface is all one color, there is no gradient at all even though the key looks correct. I have also tried:
SurfaceRenderer renderer = new SurfaceRenderer(function);
renderer.setColorScale(new GradientColorScale(new Range(yLow, yUp),
Color.BLUE, Color.YELLOW));
xyzplot.setRenderer(renderer);
and the result is the same. Here is a link to a screenshot: http://i.imgur.com/F2iYFh1.png
I found the problem. It was due to my function only return real values on discrete x and z values, which works if the set of (x,z) matches the set in my own data hash. However, the surface renderer uses the mid-point between two "x" and "z"'s in the dataset when it plots the surface to determine the color, and because the middle point isn't in the hash it returns 0, making the whole surface one color.
In this answer to my recent question, there is some code that draws a graph, but I can't manage to edit it into something that accepts any list of points as a parameter.
I'd like the Drawing method to accept these parameters:
List of Vector2, Point or VertexPositionColor, I can work with whichever.
Offset for the whole graph
These optional requirements would be appreciated:
Color that may override VertexPositionColor's color and apply to all points.
Size of the graph, so it can be shrunk or expanded, either as Vector2 as multiplier, or Point as target size. Maybe even combine this with offset in Rectangle.
And if it's possible, I'd like to have it all in a class, so graphs can be used separately from each other, each with its own Effect.world matrix, etc.
Here is that code (by Niko Drašković):
Matrix worldMatrix;
Matrix viewMatrix;
Matrix projectionMatrix;
BasicEffect basicEffect;
VertexPositionColor[] pointList;
short[] lineListIndices;
protected override void Initialize()
{
int n = 300;
//GeneratePoints generates a random graph, implementation irrelevant
pointList = new VertexPositionColor[n];
for (int i = 0; i < n; i++)
pointList[i] = new VertexPositionColor() { Position = new Vector3(i, (float)(Math.Sin((i / 15.0)) * height / 2.0 + height / 2.0 + minY), 0), Color = Color.Blue };
//links the points into a list
lineListIndices = new short[(n * 2) - 2];
for (int i = 0; i < n - 1; i++)
{
lineListIndices[i * 2] = (short)(i);
lineListIndices[(i * 2) + 1] = (short)(i + 1);
}
worldMatrix = Matrix.Identity;
viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up);
projectionMatrix = Matrix.CreateOrthographicOffCenter(0, (float)GraphicsDevice.Viewport.Width, (float)GraphicsDevice.Viewport.Height, 0, 1.0f, 1000.0f);
basicEffect = new BasicEffect(graphics.GraphicsDevice);
basicEffect.World = worldMatrix;
basicEffect.View = viewMatrix;
basicEffect.Projection = projectionMatrix;
basicEffect.VertexColorEnabled = true; //important for color
base.Initialize();
}
And the drawing method:
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
PrimitiveType.LineList,
pointList,
0,
pointList.Length,
lineListIndices,
0,
pointList.Length - 1
);
}
The Graph class that does the requested can be found here.About 200 lines of code seemed too much to paste here.
The Graph is drawn by passing a list of floats (optionally with colors) to its Draw(..) method.
Graph properties are:
Vector2 Position - the bottom left corner of the graph
Point Size - the width (.X) and height (.Y) of the graph. Horizontally, values will be distributed to exactly fit the width. Vertically, all values will be scaled with Size.Y / MaxValue.
float MaxValue - the value which will be at the top of the graph. All off the chart values (greater than MaxValue) will be set to this value.
GraphType Type - with possible values GraphType.Line and GraphType.Fill, determines if the graph will be drawn line only, or bottom filled.
The graph is drawn with a line list / triangle strip.