I'm looking to connect or glue together two shapes or objects with a Line. These shapes will be generated dynamically, meaning I'll be calling a Web service on the backend to determine how many objects/shapes need to be created. Once this is determined, I'll need to have the objects/shapes connected together.
The method signature may look like this (similar to Visio's drawing capabilities):
GlueTogether(objButton1, objButton2);
I may need to get the position of each Rectangle shape or Button to determine where the starting Line point is. Then determine the second shape/objects position to draw the line.
Any help or suggestions would be great!
Use a Path or a Line below the shapes in stacking order or z index
Use instance.TransformToVisual() to get the transform of each shape
Use the transform to transform the centerpoint of each shape
Draw a line between the two centerpoints.
var transform1 = shape1.TransformToVisual(shape1.Parent as UIElement);
var transform2 = shape2.TransformToVisual(shape2.Parent as UIElement);
var lineGeometry = new LineGeometry()
{
StartPoint = transform1.Transform(new Point(shape1.ActualWidth / 2, shape1.ActualHeight / 2.0)),
EndPoint = transform2.Transform(new Point(shape2.ActualWidth / 2.0, shape2.ActualHeight / 2.0))
};
var path = new Path()
{
Data = lineGeometry
};
I am trying much the same, but instead of the line going from one centre to the other I want the lines to stop at the edge of the two shapes.
In particular I have arrows at the end of the lines, and the arrows need to stop at the bounds of the shapes instead of going inside/behind the shape to its centre.
My shape is a usercontrol with a grid and rectangle, and some labels and other stuff.
I can't find any methods that provide me with a geometry for the edge of the shape (which is a rounded rectangle).
I figured out a solution that uses the bounding box and intersection points to connect my elements by lines at their approximate edges, and it works well for me using arrow ended lines.
See Connecting two WPF canvas elements by a line, without using anchors?
Check this out: http://www.graphspe.com/Main.aspx#/Solution/graphviz-xaml-renderer
All you have to do is printf to a string and you get your Silverlight[2|3] diagram.
Ceyhun
In addition... Instead of connecting to the center point of your objects, I've modified the same code from Michael S. to:
var lineGeometry = new LineGeometry()
{
StartPoint = transform1.Transform(new Point(1 , b1.ActualHeight / 2.0)),
EndPoint = transform2.Transform(new Point(b2.ActualWidth , b2.ActualHeight / 2.0))
};
This will connect at the outer portions of each object.
I am using the above code to draw two buttons, I want a line between those two buttons, but all i get are two buttons that look like tiny circles and no line.
code:
Button b1 = new Button();
Button b2 = new Button();
canvas1.Children.Add(b1);
canvas1.Children.Add(b2);
Canvas.SetLeft(b1, 300);
var transform1 = b1.TransformToVisual(b1.Parent as UIElement);
var transform2 = b2.TransformToVisual(b2.Parent as UIElement);
var lineGeometry = new LineGeometry()
{
StartPoint = transform1.Transform(new Point(1, b1.ActualHeight / 2.0)),
EndPoint = transform2.Transform(new Point(b2.ActualWidth, b2.ActualHeight / 2.0))
};
var path = new Path()
{
Data = lineGeometry
};
canvas1.Children.Add(path);
Related
I have a list of region borders inside SQL database and i am using sharpmap to render thumbnail images for every country i need. It works really well.
But I would like to go one step further and add a small globe around it and position country on it's place in globe, but I do not know where to start.
Here's the code i'm using so far to render country thumbs. Any ideas?
var map = new Map(new Size(command.Width, command.Height));
map.BackColor = Color.Transparent;
var countryGeometry = GeometryFromWKT.Parse(command.CountryLevelWkt);
IProvider countryProvider = new GeometryFeatureProvider(countryGeometry);
var countryLayer = new VectorLayer("country", countryProvider);
var borderColor = System.Drawing.ColorTranslator.FromHtml(command.BorderColor);
countryLayer.Style.EnableOutline = true;
countryLayer.Style.Outline = new Pen(borderColor);
countryLayer.Style.Outline.Width = command.BorderWidth;
countryLayer.Style.Fill = Brushes.Transparent;
var transformationFactory = new CoordinateTransformationFactory();
countryLayer.CoordinateTransformation = transformationFactory.CreateFromCoordinateSystems(
GeographicCoordinateSystem.WGS84,
ProjectedCoordinateSystem.WebMercator);
map.Layers.Add(countryLayer);
var bottomLeft = new Coordinate(command.Extents.BottomLeft.Longitude, command.Extents.BottomLeft.Latitude);
var topRight = new Coordinate(command.Extents.TopRight.Longitude, command.Extents.TopRight.Latitude);
// transformations
var bottomLeftLongLat = countryLayer.CoordinateTransformation.MathTransform.Transform(bottomLeft);
var topRightLongLat = countryLayer.CoordinateTransformation.MathTransform.Transform(topRight);
map.ZoomToBox(new Envelope(bottomLeftLongLat, topRightLongLat));
var img = map.GetMap();
return img;
Start by drawing all countries on a new map, each on its own layer.
Draw the country in which you're interested on its own layer.
Set map center to the Envelope.Center of the layer from Step 2. Eg, if drawing Australia, the map will move to the left.
Render map as image. Draw image on a drawing sufrace (System.Drawing.Graphics).
Re-center the map, to cover the empty space. Eg, if drawing Australia, move map almost all the way to the right. You will need to work out these offsets programmatically.
Render map from step 5 as image. Add image to the same drawing sufrace (see step 4).
Repeat steps 5-6 covering empty space below/above the render made at step 3.
Here is an example:
Note that:
Australia is in the center
There is a gap between map layers near the mouse pointer (gap in screenshot is intentional to demonstrate the logic)
Some countries are very large (eg Russia) and obtaining Envelope.Center will not work very well - consider centering based on the largest polygon only
Here's a sample Windows Forms project. In the sample, I used maps from http://thematicmapping.org/downloads/world_borders.php.
Graphics gr;
gr = CreateGraphics();
Pen p = new Pen(System.Drawing.Color.FromArgb(r.Next(255), r.Next(255), r.Next(255)), 1.1f);
Point p1 = new Point(array1[currentadd], dx);
Point p2 = new Point(array1[currentadd], dx = dx + 7);
gr.DrawLine(p, p1, p2);
now i have drawn a line and i want to write the info of points where the line is connecting them ...so i want so set label position to write the point position ... but how?
You should take a look at this document here at MSDN.
In particular something you'll notice in the example is this line:
formGraphics.DrawLine(myPen, 0, 0, 200, 200);
This is doing five different things:
First it will call our Drawing Object.
Second and third are our drawing points.
Fourth and fifth are the floating location.
This will obviously allow you to create some bounded locations. Obviously, if you've generated a class that will handle this initially to build it. Then when you call this object in another class you can position, dock, location, and anchor to that form you've called it in.
Please bear in mind that this assumes a basic line, but you can make more complex things as found here on the MSDN.
Potential Problem:
If you utilize the methodology in the example directly on your Form please bare in mind that the this will bind to the current scope of the object that the this is represented for. An example, if this code was on a button:
Pen myPen = Pen(Color.Red);
Graphics formGraphics = this.CreateGraphics();
formGraphics.DrawLine(myPen, 0, 100, 200, 200);
Then it will be bind the points based on the location of the button object in this case.
Hopefully that helps point you in the right direction.
Update:
Based on your question, it sounds like your trying to place the line either above or below a standard label. Which can be done quite simple, such as:
// Define Label
Label i = new Label();
// Create our Line.
Pen iPen = Pen(Color.Red);
Graphics iGraphics = i.CreateGraphics();
iGraphics.DrawLine(iPen, 0, 250, 0, 0);
// Dock to Bottom of Form
i.Dock = DockStyle.Bottom;
Then if you change the location at any point with a button, or you modify the text within a loop your line will remain bound to your label object. Is that not what you wanted?
I have a ZedGraphControl with a few curves in it and I want to add vertical lines at some fixed x-positions. The lines should of course only be inside the actual graph area.
I tried following
LineObj line = new LineObj(Color.Black, xPos, myPane.YAxis.Scale.Min, xPos, myPane.YAxis.Scale.Max);
line.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;
line.Line.Width = 1f;
myPane.GraphObjList.Add(line);
and this works fine until the user zooms the graph, the vertical lines will then stretch out of the actual graph area (see pic link below, also notice that it is not dashed inside the graph, odd).
http://imageshack.us/photo/my-images/196/zedgraphzoom.png/
Is there a way to solve this (if only there was a a way to get myPane.Xaxis.Scale.Min and Max of the current zoom and then update the graph in the ZoomEvent?) or are there any better classes/methods to use other than LineObj for this purpose?
Instead of defining a LineObj, define a LineItem and add it to the GraphPane.CurveList:
LineItem line = new LineItem(String.Empty, new[] { xPos, xPos },
new[] { myPane.YAxis.Scale.Min, myPane.YAxis.Scale.Max },
Color.Black, SymbolType.None);
line.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;
line.Line.Width = 1f;
myPane.CurveList.Add(line);
This binds line to the coordinate system in the graph pane, so that when you zoom or pan the line position will still be confined in the graph. Of course, if you zoom out without updating the y values of line, the line ends will be inside the graph.
I know from personal experience that dashing can be a problem in Zedgraph; however it seems like dashing is properly displayed when adding a LineItem, though.
You were on the good way using a LineObj rather than a CurveItem,.
Have a look on the Location struct and the CoordinateFrame property. It allows to use a different coordinate system for X and/or Y.
Setting the CoordinateFrame to XScaleYChartFraction allows to use 0d and 1d as Y, which means "the bottom" and "the top" of the graph pane (instead of YAxis.Scale.Min and YAxis.Scale.Max), as X continues to use the X Axis scale coordinate system.
That means you can use .AxisChange(), zoom, pan, and the LineObj will not interfer with the scale changes of the Y axis !
var line = new LineObj(Color.Black, xPos, 0, xPos, 1);
line.Location.CoordinateFrame = XScaleYChartFraction; // This do the trick !
line.IsClippedToChartRect = true;
line.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;
line.Line.Width = 1f;
myPane.GraphObjList.Add(line);
I can paint Model3DGroup in the color i want by using
Material material = new DiffuseMaterial(new SolidColorBrush(Colors.White));
GeometryModel3D model = new GeometryModel3D(mesh, material);
(following this tutorial)
to set the color of GeometryModel3D i include in the Model3DGroup.
I have an application where I have to place a map on a terrain in 3D (terrain is completely flat), the map is not an image, and I have detailed data of the points of the shapes I want to draw, and I also have DrawingVisual objects of all the shapes that I want to project on the 3D surface. In 2D mode, I draw them on a custom canvas (derived from Canvas) where I add them using
myCanvas.addVisualChild(item);
myCanvas.addLogicvalChild(item);
My question is how to "paint" or draw shapes and lines etc on 3D items? These shapes do not cover the terrain fully. I have tried using Viewport2DVisual3D class and tried placing a canvas on a 3D surface (a simple canvas with a button) using the following code:
Canvas testCanvas = new Canvas();
testCanvas.Background = Brushes.Green;
Button b1 = new Button();
b1.Content = "Hello there";
Canvas.SetTop(b1, 50);
Canvas.SetLeft(b1, 50);
Canvas.SetZIndex(b1, 2);
testCanvas.Children.Add(b1);
testVisual3d.Visual = testCanvas; // testVisual3d is a Viewport2DVisual3D declared in xaml
But the problem is that I am not able to figure out how the Canvas or any Visual "fills" the Viewport2DVisual3D class because:
The button filled the canvas completely.
Empty areas of the canvas (Canvas.SetTop(b1, 50)) is not visible.
I have no idea idea about the size of the canvas related to the size of Viewport2DVisual3D object because the button always fills the object completely.
Also i cannot use Viewport2DVisual3D everywhere, as I also have to create models of buildings etc. when I am projecting the map to a 3D terrain, so I'll have to paint areas of a building model (which will be a Model3DGroup) differently to give a realistic effects, but if i manage to project the map on a Viewport2DVisual3D, it'll solve lots of problems as i'll be able to directly project all the shapes including grid on the Viewport2DVisual3D object or terrain.
I am using .NET 4.0 and C#.
Please help.
Update
Using this code solves the initial problem of canvas size and space:
Canvas testCanvas = new Canvas();
testCanvas.Background = Brushes.Green;
Button b1 = new Button();
b1.Width = 120;
b1.Height = 25;
testCanvas.Width = 200;
testCanvas.Height = 200;
b1.Content = "Hello there";
Canvas.SetTop(b1, 50);
Canvas.SetLeft(b1, 50);
Canvas.SetZIndex(b1, 2);
testCanvas.Children.Add(b1);
testVisual3d.Visual = testCanvas;
The size of Viewport2DVisual3D is
Positions="0,0,0 0,0,30 30,0,30 30,0,0"
And the canvas resizes to fit the boundaries of the Viewport2DVisual3D, but will it work with a class derived from Canvas in which shapes have directly been added using Canvas.AddVisualChild and Canvas.AddLogicalChild, i am yet to try that.
And also the original question of painting on the Model3DGroup remains, how to do it?
This may be a bit late, but I was having issues with a very similar problem. Though the above works fine for smaller meshes, once you get into the hundreds and thousands (or more) of triangles, it becomes slow. The alternative I have implemented uses a uniform material for all meshes/collections but uses a lineargradientbrush to assign a gradient color to the material (stops and colors set as needed for what you are trying to achieve). Then by using texture coordinates, the color of a given triangle can be set (all three texture coordinates would be the same point if you want to have a solid color). This approach works far better and is more efficient for large meshes (10k and up) with only extra time required to set up the brush and assigning texture coordinates. The same/similar approach can also be used for changing the color of a triangle through hit-testing or similar whereby only the triangle's vertices (index) are provided and the source mesh (thus the index of the texture coordinates that need updating are provided)
So here's some code that I've used (with some modifications, adapt as necessary). This is in VB, porting to C# should be easy. Note that this is a copy-paste from some existing project, so some items may not be as clear as they could be... sorry for being lazy...
Private Sub RefreshRHDisplay()
'global/input data is RHTris which is a List(of Point3D())
'updates the data on the display with the available data
Dim v, f, i As Integer
'clear geometry
Group3D_RH.Children.Clear() 'is a Model3DGroup object
'define lights
Dim _L As Model3DGroup = GetLighting() 'defines the lighting
'mesh items
Dim mesh As New MeshGeometry3D
'group of items
Dim geomGroup As New Model3DGroup
'materials
Dim mat As MaterialGroup 'the foreground/front material
Dim bmat As MaterialGroup 'the background/back material
Dim MatGroup As New MaterialGroup
Dim ColBrush As Brush = New SolidColorBrush(_bodyCol) : ColBrush.Opacity = 1.0F
Dim LightBrush As Brush = New SolidColorBrush(Colors.White) : LightBrush.Opacity = 0.3
Dim _diffuseMaterial As New DiffuseMaterial(ColBrush)
Dim _specularMaterial As New SpecularMaterial(LightBrush, 300.0F)
MatGroup.Children.Add(_diffuseMaterial)
MatGroup.Children.Add(_specularMaterial)
mat = MatGroup
MatGroup = New MaterialGroup
Dim bColBrush As Brush = New SolidColorBrush(Colors.DarkGray) : bColBrush.Opacity = 1.0F
_diffuseMaterial = New DiffuseMaterial(bColBrush)
MatGroup.Children.Add(_diffuseMaterial)
MatGroup.Children.Add(_specularMaterial)
bmat = MatGroup
Dim vPts(0) As Point
'distinguish between direct (uniform color) and colorize mode
If chkColorize.IsChecked Then 'COLORIZE MODE
'Note: the gradient stop ends at 0.9, this is purely so that I can assign a special color after that.
Dim gsp As New GradientStopCollection({New GradientStop(Colors.Red, 0.0),
New GradientStop(Colors.Orange, 0.15),
New GradientStop(Colors.Yellow, 0.3),
New GradientStop(Colors.Green, 0.45),
New GradientStop(Colors.Blue, 0.6),
New GradientStop(Colors.Indigo, 0.75),
New GradientStop(Colors.Violet, 0.9)
})
Dim lBrsh As New LinearGradientBrush(gsp, New Point(0, 1), New Point(0.9, 1))
'define random locations for the textures
ReDim vPts(RHTris.Count - 1)
Dim rnd As New Random(CInt(Now.Ticks Mod Integer.MaxValue))
For v = 0 To RHTris.Count - 1
vPts(v) = New Point(rnd.NextDouble, 1)
Next
'replace the default material with the linear gradient
mat.Children.Clear()
mat.Children.Add(New DiffuseMaterial(lBrsh))
End If
'add all vertices
For v = 0 To RHTris.Count - 1
mesh.Positions.Add(RHTris.Item(v)(0))
mesh.Positions.Add(RHTris.Item(v)(1))
mesh.Positions.Add(RHTris.Item(v)(2))
Next
'add all triangles
v = 0
For f = 0 To RHTris.Count - 1
'If .isVisible Then 'true by def of _visList
Dim v0, v1, n As Vector3D
v0 = Point3D.Subtract(RHTris.Item(f)(1), RHTris.Item(f)(0))
v1 = Point3D.Subtract(RHTris.Item(f)(2), RHTris.Item(f)(0))
n = Vector3D.CrossProduct(v0, v1)
mesh.Normals.Add(n)
mesh.Normals.Add(n)
mesh.Normals.Add(n)
mesh.TriangleIndices.Add(v)
mesh.TriangleIndices.Add(v + 1)
mesh.TriangleIndices.Add(v + 2)
v += 3
Next
If chkColorize.IsChecked Then 'define the texture coordinates that define the color through the linear gradient brush
For v = 0 To RHTris.Count - 1
For i = 0 To 2
mesh.TextureCoordinates.Add(vPts(v)) 'add same three points for each location
Next
Next
End If
'body mesh has been defined, create body and add to visual
Dim geo As New GeometryModel3D
geo.Geometry = mesh
geo.Material = mat
geo.BackMaterial = bmat
'combine to group
geomGroup.Children.Add(geo)
'add to scene
Group3D_RH.Children.Add(geomGroup)
'add lights back in
Group3D_RH.Children.Add(_L)
End Sub
Some basic comments are in order: the data is in the RHTris collection which is a variable of type List(of Point3D()) which contains three locations. The color assigned to each is either a default material or a random color (if chkColorize is checked). The Sub also applies the data to the Group3D_RH which is a Model3DGroup object.
The approach uses either a solid material or a (constant) texture that has a color with a gradient (going through all the colors that are then available). The assignment of color is then to define a point (2D point) to the texture coordinates. If each point of the triangle has the same point assigned then the triangle ends up having a uniform color (based on its coordinates along the color gradient)...
Hope this at least illustrates the approach...
After a bit of experimenting, i have the answer, simple way is to set the color of each triangle while defining the mesh. In WPF, what is needed is to set the Material color when combining the triangle (MeshGeometry3D) and material while defining a new GeometryModel3D.
(from this tutorial)
Material material = new DiffuseMaterial(
new SolidColorBrush(Colors.DarkKhaki)); //Set color here
GeometryModel3D model = new GeometryModel3D(
mesh, material);
I am a beginner of 3D and I have not yet explored the concept of materials.
If someone knows another way, please post here.
I have spent 20 minutes trying to find an example in C# of how to draw a line between two rectangles, and I can't find anything. I don't know if I just don't understand the paradigm of drawing 2D shapes in Silverlight, or if I'm just looking in the wrong place.
I have set up the rectangles so I can drag them around, and now I want to draw a line between the two shapes as I drag a rectangle across the canvas. I want to be able to do something like this as I drag the second rectangle:
void host1_MouseLeftButtonMove(object sender, MouseEventArgs e)
{
if (isDown)
{
this.host1TranslateTransform.X = e.GetPosition(canvas).X - x;
this.host1TranslateTransform.Y = e.GetPosition(canvas).Y - y;
Line l = new Line();
l.X1 = rect1.X; // does not work
l.X2 = e.GetPosition(canvas).X;
l.Y1 = rect1.Y; // does not work
l.Y2 = e.GetPosition(canvas).Y;
}
}
How do I get the coordinates of the first box? I can't figure out how to get the coordinates of shapes relative to the canvas in my application. I would appreciate any tutorials that give a beginner overview for how to draw simple 2D shapes.
Thanks!
try this
Canvas.GetTop(element);
Canvas.GetLeft(element);
position properties are attached properties ;)