Hello my good people of stackOverflow,
Recently, i was building a component where a certain dropDownList component with id = languageCombo, has to be relocated dynamically as the states change..here is the code for the two states: initial and chaingrp. i want the dropDownList component to change from one position X in the initial state to another position X = 500 in the tree_changeHandler function when the chaingrp state is called but the dropDownList component doesn't change. Could anyone point me to what the problem could be?
Thanks
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:components="components.*"
xmlns:searchComponents="searchComponents.*"
minHeight="500" minWidth="500">
<fx:Metadata>
[Event(name="SearchClick", type="eventClass.SelectedTableEvent")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import eventClass.SelectedTableEvent;
import mx.collections.ArrayCollection;
import mx.events.ListEvent;
import mx.events.TreeEvent;
import spark.events.IndexChangeEvent;
namespace simo="http://www.simo-project.org/simo";
[Bindable]
public var parentXmlCopy:XML;
[Bindable]
public var treeXml:XML;
[Bindable]
private var newXml:XML;
private var transferredname:String;
public var modelType:String;
public var selectedModelchainIndex:int;
public var selectedTaskIndex:int;
public function set variables(_xml:XML):void{
parentXmlCopy = _xml;
}
private function languageChange(event:Event):void{
trace("languageChange");
var cb:DropDownList=event.target as DropDownList;
var temp:Object=cb.selectedItem;
var chain:Array=[];
for (var i:int=0; i < cb.dataProvider.length; i++)
{
if(cb.dataProvider[i].label == temp.label)
{
chain.push(cb.dataProvider[i].locale);
resourceManager.localeChain=chain;
}
}
}
protected function tree_changeHandler(event:ListEvent):void
{
use namespace simo;
var selectedItem:XML;
transferredname = tree.selectedItem.localName();
switch (transferredname){
case "chain_group" : currentState = "chaingroup";
languageCombo.X = 500;
selectedItem = XML(tree.selectedItem);
trace(selectedItem);
this.chainGrpId.displayChainGrp(selectedItem);
break;
}
}
private function treeLabel(item:Object):String
{
return item.localName();
}
]]>
</fx:Script>
<s:states>
<s:State name="initial"/>
<s:State name="chaingroup"/>
</s:states>
<s:HGroup><!--overall outer Hgroup-->
<s:HGroup gap="8">
<s:HGroup gap="5">
<s:VGroup height="460" paddingLeft="10" paddingTop="5">
<s:HGroup gap="3">
<searchComponents:AutoComplete/>
<s:Button
id="searchBT"
height="24"
width="24"
skinClass="skins.searchBTskin"/>
</s:HGroup>
<s:List id="treeDP" height="120" width="180" labelField="#name"
change="treeDP_changeHandler(event)">
<s:dataProvider>
<s:XMLListCollection source="{parentXmlCopy.children()}" />
</s:dataProvider>
</s:List>
<s:Button id="delListBT" label="delList"/>
<s:HGroup gap="70">
<s:Button
id="backBT"
label="back"
height="24"
width="24"
/>
<s:Button
id="nextBT"
label="next"
height="24"
width="24"
/>
</s:HGroup>
<mx:Tree id="tree" dataProvider="{treeXml}" labelField="{treeXml.localName()}"
top="72" left="50" labelFunction="treeLabel"
maxHorizontalScrollPosition="20"
maxVerticalScrollPosition="10"
showRoot="true"
change="tree_changeHandler(event)"
height="225" width="180"/>
<s:Button id="delTreeBT" label="delTree"/>
</s:VGroup>
<s:Group includeIn="chaingroup/>
</s:HGroup>
</s:HGroup>
<s:HGroup paddingLeft="200" paddingTop="5"
x.modelOperation="-38" y.modelOperation="-22" width.modelOperation="737"
x.modelPrediction="-18" y.modelPrediction="-20">
<s:DropDownList id="languageCombo" width="150" change="languageChange(event)" includeIn="initial"
dataProvider="{new ArrayCollection([{locale:'fi_FI',label:'Suomi'}, {locale:'en_US', label:'English'}])}"
prompt="{resourceManager.getString('modelChainUI','lang')}"
x.chaingroup="900"
x.condition="900"
x.modelchain="900"
x.task="900"/>
</s:HGroup>
</s:HGroup><!--overall hgroup ends here!-->
<s:VGroup gap="5" paddingLeft="10"
x.chaingroup="144" y.chaingroup="-69" height.chaingroup="148">
<components:ChainGroup id="chainGrpId" includeIn="chaingroup"/>
</s:VGroup>
Your languageCombo is within HGroup. HGroup is a container with horizontal layout. Horizontal layout doesn't consider any x and y coordinates of children (because of it is horizontal layout).
Maybe it better to use simple Group (taking in mind your HGroup has only one child — so horizontal layout hasn't sense)?
Related
What I'm trying to do is insert svg circles by clicking button to the workspace. Beside that, I want to free drag all those circles.
Can you help me the code?
document.getElementById('draw').addEventListener('click', function(){
document.getElementById('here').innerHTML =
'<svg height="100" width="100">' +
'<circle cx="50" cy="50" r="40" stroke="black" stroke-width="1" fill="rgba(130,130,130,0.6)">' +
'</svg>';
});
<button id="draw">Draw Circle</button>
<div id="here"></div>
I was amazed that creating an SVG like this would work, and it works! (on IE too). However it creates problems when trying to work with events. I prefer to create the SVG element and the circle element using createElementNS and use appendChild to append them to the DOM
const SVG_NS = 'http://www.w3.org/2000/svg';
const SVG_XLINK = "http://www.w3.org/1999/xlink";
/*let innerSVG = '<svg height="100" width="100">' +
'<circle cx="50" cy="50" r="40" stroke="black" stroke-width="1" fill="rgba(130,130,130,0.6)">' +
'</svg>';*/
let svgdata = {width:100,height:100}
let circledata = {cx:50,cy:50,r:40}
// creating a new SVG element using the data
let svg = newSVG(svgdata);
// creating a new circle element using the data and appending it to the svg
let circle = drawCircle(circledata, svg);
// the offset between the click point on the SVG and the left upper corner of the SVG
let offset={}
// a flag to control the dragging
let dragging = false;
// the mouse position
let m;
document.getElementById('draw').addEventListener('click', function(){
here.appendChild(svg)});
// events
here.addEventListener("mousedown",(evt)=>{
dragging = true;
offset = oMousePos(svg, evt);
})
here.addEventListener("mousemove",(evt)=>{
if(dragging){
m = oMousePos(here, evt);
svg.style.top = (m.y - offset.y)+"px";
svg.style.left = (m.x - offset.x)+"px";
}
})
here.addEventListener("mouseup",(evt)=>{
dragging = false;
})
function drawCircle(o, parent) {
var circle = document.createElementNS(SVG_NS, 'circle');
for (var name in o) {
if (o.hasOwnProperty(name)) {
circle.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(circle);
return circle;
}
function newSVG(o) {
let svg = document.createElementNS(SVG_NS, 'svg');
for (var name in o) {
if (o.hasOwnProperty(name)) {
svg.setAttributeNS(null, name, o[name]);
}
}
return svg;
}
function oMousePos(elmt, evt) {
let ClientRect = elmt.getBoundingClientRect();
return {
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
svg{border:1px solid;position:absolute;}
circle{
stroke:black;stroke-width:1;fill:rgba(130,130,130,0.6);
}
#here{width:100%; height:100vh; background-color:#efefef;margin:0; padding:0; position:relative}
<button id="draw">Draw Circle</button>
<div id="here"></div>
So I was spending some time playing around with pure (no external libraries) SVG elements dragging.
In general all works, but there is this nasty issue for fast moving mouse:
- when user mousedowns a draggable SVG element close to its edge
- then drags (mousemove) such draggable too fast
- the mouse "loses" the draggable
Here the issue is described in more details:
http://www.svgopen.org/2005/papers/AdvancedMouseEventModelForSVG-1/index.html#S3.2
Also here the author tried to fix UX by leveraging mouseout event:
http://nuclearprojects.com/blog/svg-click-and-drag-object-with-mouse-code/
I copied the above code snippet here: http://codepen.io/cmer41k/pen/zNGwpa
The question I have is:
Is there no other way (provided by pure SVG) to prevent such "loss" of SVG element while mouse moves too fast?
My attempt to solve this was:
- detect (somehow) that mouseout event happened without finishing the dragging.
- and if so (we sort of detected "disconnect") - reconnect the SVG element with current mouse position.
Is there a reason why this wouldn't work?
Code:
var click=false; // flag to indicate when shape has been clicked
var clickX, clickY; // stores cursor location upon first click
var moveX=0, moveY=0; // keeps track of overall transformation
var lastMoveX=0, lastMoveY=0; // stores previous transformation (move)
function mouseDown(evt){
evt.preventDefault(); // Needed for Firefox to allow dragging correctly
click=true;
clickX = evt.clientX;
clickY = evt.clientY;
evt.target.setAttribute("fill","green");
}
function move(evt){
evt.preventDefault();
if(click){
moveX = lastMoveX + ( evt.clientX – clickX );
moveY = lastMoveY + ( evt.clientY – clickY );
evt.target.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
}
}
function endMove(evt){
click=false;
lastMoveX = moveX;
lastMoveY = moveY;
evt.target.setAttribute("fill","gray");
}
The most important part of your code is missing, namely how or more specifically on which element you register the events.
What you basically do to prevent this problem is to register the mousemove and mouseup events on the outermost svg element, and not on the element you want to drag.
svg.addEventListener("mousemove", move)
svg.addEventListener("mouseup", endMove)
When starting the drag, register the events on the svg element, and when done unregister them.
svg.removeEventListener("mousemove", move)
svg.removeListener("mouseup", endMove)
you have to store the element you are currently dragging, so it is available in the other event handlers.
what i additionally do is to set pointer-events to "none" on the dragged
element so that you can react to mouse events underneath the dragged element (f.e. finding the drop target...)
evt.target.setAttribute("pointer-events", "none")
but don't forget to set it back to something sensible when dragging is done
evt.target.setAttribute("pointer-events", "all")
var click = false; // flag to indicate when shape has been clicked
var clickX, clickY; // stores cursor location upon first click
var moveX = 0,
moveY = 0; // keeps track of overall transformation
var lastMoveX = 0,
lastMoveY = 0; // stores previous transformation (move)
var currentTarget = null
function mouseDown(evt) {
evt.preventDefault(); // Needed for Firefox to allow dragging correctly
click = true;
clickX = evt.clientX;
clickY = evt.clientY;
evt.target.setAttribute("fill", "green");
// register move events on outermost SVG Element
currentTarget = evt.target
svg.addEventListener("mousemove", move)
svg.addEventListener("mouseup", endMove)
evt.target.setAttribute("pointer-events", "none")
}
function move(evt) {
evt.preventDefault();
if (click) {
moveX = lastMoveX + (evt.clientX - clickX);
moveY = lastMoveY + (evt.clientY - clickY);
currentTarget.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
}
}
function endMove(evt) {
click = false;
lastMoveX = moveX;
lastMoveY = moveY;
currentTarget.setAttribute("fill", "gray");
svg.removeEventListener("mousemove", move)
svg.removeEventListener("mouseup", endMove)
currentTarget.setAttribute("pointer-events", "all")
}
<svg id="svg" width="800" height="600" style="border: 1px solid black; background: #E0FFFF;">
<rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
<circle id="mycirc" cx="60" cy="60" r="22" onmousedown="mouseDown(evt)" />
</svg>
more advanced
there are still two things not so well with this code.
it does not work for viewBoxed SVGs nor for elements inside
transformed parents.
all the globals are bad coding practice.
here is how to fix those:
Nr. 1 is solved by converting mouse coordinates into local coordinates using the inverse of getScreenCTM (CTM = Current Transformation Matrix).
function globalToLocalCoords(x, y) {
var p = elem.ownerSVGElement.createSVGPoint()
var m = elem.parentNode.getScreenCTM()
p.x = x
p.y = y
return p.matrixTransform(m.inverse())
}
For nr. 2 see this implementation:
var dre = document.querySelectorAll(".draggable")
for (var i = 0; i < dre.length; i++) {
var o = new Draggable(dre[i])
}
function Draggable(elem) {
this.target = elem
this.clickPoint = this.target.ownerSVGElement.createSVGPoint()
this.lastMove = this.target.ownerSVGElement.createSVGPoint()
this.currentMove = this.target.ownerSVGElement.createSVGPoint()
this.target.addEventListener("mousedown", this)
this.handleEvent = function(evt) {
evt.preventDefault()
this.clickPoint = globalToLocalCoords(evt.clientX, evt.clientY)
this.target.classList.add("dragged")
this.target.setAttribute("pointer-events", "none")
this.target.ownerSVGElement.addEventListener("mousemove", this.move)
this.target.ownerSVGElement.addEventListener("mouseup", this.endMove)
}
this.move = function(evt) {
var p = globalToLocalCoords(evt.clientX, evt.clientY)
this.currentMove.x = this.lastMove.x + (p.x - this.clickPoint.x)
this.currentMove.y = this.lastMove.y + (p.y - this.clickPoint.y)
this.target.setAttribute("transform", "translate(" + this.currentMove.x + "," + this.currentMove.y + ")")
}.bind(this)
this.endMove = function(evt) {
this.lastMove.x = this.currentMove.x
this.lastMove.y = this.currentMove.y
this.target.classList.remove("dragged")
this.target.setAttribute("pointer-events", "all")
this.target.ownerSVGElement.removeEventListener("mousemove", this.move)
this.target.ownerSVGElement.removeEventListener("mouseup", this.endMove)
}.bind(this)
function globalToLocalCoords(x, y) {
var p = elem.ownerSVGElement.createSVGPoint()
var m = elem.parentNode.getScreenCTM()
p.x = x
p.y = y
return p.matrixTransform(m.inverse())
}
}
.dragged {
fill-opacity: 0.5;
stroke-width: 0.5px;
stroke: black;
stroke-dasharray: 1 1;
}
.draggable{cursor:move}
<svg id="svg" viewBox="0 0 800 600" style="border: 1px solid black; background: #E0FFFF;">
<rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
<circle class="draggable" id="mycirc" cx="60" cy="60" r="22" fill="blue" />
<g transform="rotate(45,175,75)">
<rect class="draggable" id="mycirc" x="160" y="60" width="30" height="30" fill="green" />
</g>
<g transform="translate(200 200) scale(2 2)">
<g class="draggable">
<circle cx="0" cy="0" r="30" fill="yellow"/>
<text text-anchor="middle" x="0" y="0" fill="red">I'm draggable</text>
</g>
</g>
</svg>
<div id="out"></div>
I have a xaml page in universal windows application in windows 10. This page contains two listboxes. Both listboxes have the same ItemsSource like
public class CategoryModel
{
public int CategoryId {get; set;}
public string CategoryName {get; set;}
public List<string> ImageURL {get; set;}
}
The top listbox creates Menu header at the top in Horizontal manner and the bottom listbox creates Menu data at the bottom of the menu header in Vertical manner.
The problem is that how to know which menu data element at the bottom is in focus, so that I can highlight the same element in Menu header?
<ListView x:Name="lvMenuBar" Grid.Column="1" FlowDirection="LeftToRight" ItemsSource="{Binding MenuCategories}" Width="Auto">
<ListView.ItemTemplate>
<DataTemplate>
<Button Click="MenuBarClick" HorizontalAlignment="Center" VerticalAlignment="Top" Tag="{Binding CategoryId}" Content="{Binding CategoryName}" Style="{StaticResource CustomButtonStyle}" FontFamily="Segoe UI" FontWeight="SemiBold" FontSize="18" Margin="0" Padding="20" BorderBrush="Red" BorderThickness="0" Opacity="0.5" Foreground="Black"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<ListView x:Name="lvMenuBar" Grid.Column="1" FlowDirection="LeftToRight" ItemsSource="{Binding MenuCategories}" Width="Auto">
<ListView.ItemTemplate>
<DataTemplate>
The above is my XAML
If I understand the question correctly, you want to select (or otherwise highlight) an item in the top list whenever that item is selected in the bottom list. You can do this with data binding, e.g.:
<ListView
Grid.Row="0"
ItemsSource="{Binding MenuCategories}"
Margin="8"
SelectedIndex="{Binding SelectedIndex, ElementName=verticalList, Mode=OneWay}"
>
Here the SelectedIndex property of the top list mirrors that of the bottom list.
In context, something like this:
<Page.Resources>
<Style x:Key="CustomButtonStyle" TargetType="Button" />
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListView
Grid.Row="0"
ItemsSource="{Binding MenuCategories}"
Margin="8"
SelectedIndex="{Binding SelectedIndex, ElementName=verticalList, Mode=OneWay}"
>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:CategoryModel">
<Button
HorizontalAlignment="Center"
VerticalAlignment="Top"
Tag="{Binding CategoryId}"
Content="{Binding CategoryName}"
Style="{StaticResource CustomButtonStyle}"
FontFamily="Segoe UI"
FontWeight="SemiBold"
FontSize="18"
Margin="0"
Padding="20"
BorderBrush="Red"
BorderThickness="0"
Opacity="0.5"
Foreground="Black"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<ListView
x:Name="verticalList"
Grid.Row="1"
ItemsSource="{Binding MenuCategories}"
Margin="8"
IsItemClickEnabled="True"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:CategoryModel">
<Button
HorizontalAlignment="Center"
VerticalAlignment="Top"
Tag="{Binding CategoryId}"
Content="{Binding CategoryName}"
Style="{StaticResource CustomButtonStyle}"
FontFamily="Segoe UI"
FontWeight="SemiBold"
FontSize="18"
Margin="0"
Padding="20"
BorderBrush="Red"
BorderThickness="0"
Opacity="0.5"
Foreground="Black"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
Caveat: When you click one of your buttons, the ListView doesn't see that click; if you want that click to select an item, do it in code-behind.
Note also the IsItemClickEnabled property on the second (vertical) list.
EDIT: If I understand you correctly, you want the selection in the upper horizontal list to track scrolling in the lower vertical instead of selection. In that case you need to get hold of the built-in ScrollViewer and do something like this:
public MainPage()
{
InitializeComponent();
DataContext = this;
Loaded += (sender, args) =>
{
ScrollViewer scrollViewer = FindVisualChild<ScrollViewer>(verticalList);
if (scrollViewer != null)
{
scrollViewer.ViewChanged += (o, eventArgs) =>
{
int length = MenuCategories.Length;
double offset = scrollViewer.VerticalOffset;
double height = scrollViewer.ExtentHeight;
int index = (int)(length * offset / height);
horizontalList.SelectedIndex = index;
};
}
};
}
private static T FindVisualChild<T>(DependencyObject parent)
where T : DependencyObject
{
if (parent != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
T candidate = child as T;
if (candidate != null)
{
return candidate;
}
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return default(T);
}
You'll probably need to experiment with the calculations here; this is a bit experimental on my part.
This piece of code has solved much of my issue
private double _scrollExtentHeight;
private ScrollViewer _scrollViewer;
_scrollViewer = FindVisualChild<ScrollViewer>(lvMenuItems);
if (_scrollViewer != null)
{
_scrollViewer.ManipulationMode = ManipulationModes.TranslateY;
_scrollViewer.DirectManipulationCompleted += scrollViewerDirectManipulationCompleted;
_scrollExtentHeight = _scrollViewer.ExtentHeight;
}
private static T FindVisualChild<T>(DependencyObject parent)
where T : DependencyObject
{
if (parent != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
T candidate = child as T;
if (candidate != null)
{
return candidate;
}
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return default(T);
}
private void scrollViewerDirectManipulationCompleted(object sender, object e)
{
_menuVM.StartDispatcher();
if (_scrollViewer != null)
{
int length = _menuVM.Categories.Count;
double offset = _scrollViewer.VerticalOffset;
//Horizontal scroll viewer
List<Button> menuItems = GetAllMenuItemControl(lvMenuBar);
int currIndex = 0, index = 0;
//Categories height ratio contains the height ratio of each element for total height
for (; index < _menuVM.CategoriesHeightRatio.Count; index++)
{
if ((_menuVM.CategoriesHeightRatio[index - 1 > 0 ? index - 1 : 0] * _scrollExtentHeight) < offset && (_menuVM.CategoriesHeightRatio[index] * _scrollExtentHeight) >= offset)
{
currIndex = index;
}
else
{
menuItems[index].BorderThickness = new Thickness(0, 0, 0, 0);
menuItems[index].Opacity = 0.5;
}
}
menuItems[currIndex].BorderThickness = new Thickness(0, 0, 0, 2);
menuItems[currIndex].Opacity = 1;
var transform = lvMenuBar.TransformToVisual(menuItems[currIndex]);
Point absolutePoint = transform.TransformPoint(new Point(0, 0));
svMenuBar.ChangeView(Math.Abs(absolutePoint.X), null, null, false);
}
}
private List<Button> GetAllMenuItemControl(DependencyObject parent)
{
var _List = new List<Button>();
for (int index = 0; index < VisualTreeHelper.GetChildrenCount(parent); index++)
{
var _Child = VisualTreeHelper.GetChild(parent, index);
if (_Child is Button)
_List.Add(_Child as Button);
_List.AddRange(GetAllMenuItemControl(_Child));
}
return _List;
}
Say I have the following SVG:
<g id="g1" transform="translate(100, 250) rotate(90)" >
<path id="path1" d="M 100,100 c...
Is there a way to get the actual coordinates of the d attribute? Ideally I want some function such as untransform below:
var transform = $("g1").getAttribute("transform");
var d = $("path1").getAttribute("d");
var dUntransformed = untransform(d, transform);
$("g1").removeAttribute("transform");
$("path1").setAttribute("d", dUntransformed);
The idea is that if I ran this script the resulting image would be identical to the previous image.
The reason I want to do this is because I have an animation that follows this path. However because of the transform the animation is off. And if I add the transform to the animateMotion object the animation is still off. So my thought is to remove the path and put it back exactly where it is. So that I can get the animation to work. (The AnimateMotion is similar to this demo: https://mdn.mozillademos.org/files/3261/animateMotion.svg)
Here is an example to return screen points after tranformations : polygon, path, polyline. The path example does not work or arcs. Also, in some cases relative points may not return. This uses getCTM and matrixTransform. It may be possible to create an array to 'remember' the points at various time lines.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Return Screen Points After Tranformations</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style='padding:0px;font-family:arial'>
<center>
<h4>Return Screen Points After Tranformations : polygon, path, polyline</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:10px;'>
In many cases it is meaningful to return certain svg elements(polygon, polyline, and path) to their screen x,y values following transformations. This is accomplished using <b>getCTM</b>, and <b>matrixTransform</b>
Note: Use vector-effect="non-scaling-stroke" for elements with stroke(*not available in IE).
</div>
<div id="svgDiv" style="background-color:lightgreen;width:500px;height:500px;">
<svg id="mySVG" width="500" height="500">
<path id="myPath" vector-effect="non-scaling-stroke" transform="scale(.8)translate(120 50)skewY(15)rotate(-15)" fill="yellow" stroke="black" stroke-width="2"
d="M50,50 Q-30,100 50,150 100,230 150,150 230,100 150,50 100,-30 50,50"/>
<polyline vector-effect="non-scaling-stroke" id="myPolyline" transform="scale(.3)translate(700 620)" fill="red" stroke="black" stroke-width="3" points="122 60 150 450 500 400" />
<polygon vector-effect="non-scaling-stroke" id="myPolygon" transform="scale(3)translate(122 132)" fill="purple" stroke="white" stroke-width="3"points="15,0 10.6066,-10.6066 9.18486e-016,-15 -10.6066,-10.6066 -15,-1.83697e-015 -10.6066,10.6066 -2.75545e-015,15 10.6066,10.6066" />
</svg>
</div>
<button onClick=change2Screen()>change to screen values</button>
<br />SVG Source:<br />
<textarea id=mySVGValue style='font-size:120%;font-family:lucida console;width:90%;height:200px'></textarea>
<br />Javascript:<br />
<textarea id=jsValue style='border-radius:26px;font-size:110%;font-weight:bold;color:midnightblue;padding:16px;background-color:beige;border-width:0px;font-size:100%;font-family:lucida console;width:90%;height:400px'></textarea>
</center>
<div id='browserDiv' style='padding:3px;position:absolute;top:5px;left:5px;background-color:gainsboro;'></div>
<script id=myScript>
//--button---
function change2Screen()
{
screenPolyline(myPolyline)
screenPolygon(myPolygon)
screenPath(myPath)
mySVGValue.value=svgDiv.innerHTML
}
function screenPolyline(myPoly)
{
var sCTM = myPoly.getCTM()
var svgRoot = myPoly.ownerSVGElement
var pointsList = myPoly.points;
var n = pointsList.numberOfItems;
for(var m=0;m<n;m++)
{
var mySVGPoint = svgRoot.createSVGPoint();
mySVGPoint.x = pointsList.getItem(m).x
mySVGPoint.y = pointsList.getItem(m).y
mySVGPointTrans = mySVGPoint.matrixTransform(sCTM)
pointsList.getItem(m).x=mySVGPointTrans.x
pointsList.getItem(m).y=mySVGPointTrans.y
}
//---force removal of transform--
myPoly.setAttribute("transform","")
myPoly.removeAttribute("transform")
}
//---except arc/relative paths---
function screenPath(path)
{
var sCTM = path.getCTM()
var svgRoot = path.ownerSVGElement
var segList=path.pathSegList
var segs=segList.numberOfItems
//---change segObj values
for(var k=0;k<segs;k++)
{
var segObj=segList.getItem(k)
if(segObj.x && segObj.y )
{
var mySVGPoint = svgRoot.createSVGPoint();
mySVGPoint.x = segObj.x
mySVGPoint.y = segObj.y
mySVGPointTrans = mySVGPoint.matrixTransform(sCTM)
segObj.x=mySVGPointTrans.x
segObj.y=mySVGPointTrans.y
}
if(segObj.x1 && segObj.y1)
{
var mySVGPoint1 = svgRoot.createSVGPoint();
mySVGPoint1.x = segObj.x1
mySVGPoint1.y = segObj.y1
mySVGPointTrans1 = mySVGPoint1.matrixTransform(sCTM)
segObj.x1=mySVGPointTrans1.x
segObj.y1=mySVGPointTrans1.y
}
if(segObj.x2 && segObj.y2)
{
var mySVGPoint2 = svgRoot.createSVGPoint();
mySVGPoint2.x = segObj.x2
mySVGPoint2.y = segObj.y2
mySVGPointTrans2 = mySVGPoint2.matrixTransform(sCTM)
segObj.x2=mySVGPointTrans2.x
segObj.y2=mySVGPointTrans2.y
}
}
//---force removal of transform--
path.setAttribute("transform","")
path.removeAttribute("transform")
}
//---changes all transformed points to screen points---
function screenPolygon(myPoly)
{
var sCTM = myPoly.getCTM()
var svgRoot = myPoly.ownerSVGElement
var pointsList = myPoly.points;
var n = pointsList.numberOfItems;
for(var m=0;m<n;m++)
{
var mySVGPoint = svgRoot.createSVGPoint();
mySVGPoint.x = pointsList.getItem(m).x
mySVGPoint.y = pointsList.getItem(m).y
mySVGPointTrans = mySVGPoint.matrixTransform(sCTM)
pointsList.getItem(m).x=mySVGPointTrans.x
pointsList.getItem(m).y=mySVGPointTrans.y
}
//---force removal of transform--
myPoly.setAttribute("transform","")
}
</script>
<script>
document.addEventListener("onload",init(),false)
function init()
{
mySVGValue.value=svgDiv.innerHTML
jsValue.value=myScript.text
}
</script>
</body>
</html>
All,
I have a SVG rectangle in my application which can be stretched horizontally by dragging the end bar (left & right) on either side of the rectangle. The rectangle can be
(1) resized (by stretching as per above),
(2)dragged,
(3)& rotated.
Everything works fine, however, one strange experience is that when I rotate the rectangle to a degree close to 90, & then try to resize the rectangle, it starts stretching from the opposite border of the rectangle instead of the original borders. (here is the image):
It appears to be getting confused between left and right when I use the rotate function.
Here is the revised HTML, JS & SVG:
<%#page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
<!-- <script type="text/javascript" src="CPolyline.js">
</script>-->
</head>
<body>
<object id="oo" data="rect2.svg" style="position:fixed;width: 800px;height:800px;bottom:-100px;right: 375px;">
</object>
path: <input type="button" id="path" onclick="X()">
path2: <input type="button" id="path2" onclick="Y()">
<input type="button" value="Rotate" onclick="Rotate1()">
<script type="text/javascript">
var ob=document.getElementById("oo")
var svgDoc=null;
var svgRoot=null;
var MyGroupObjectsObj = null;
var svgNS = "http://www.w3.org/2000/svg";
var dragTarget = null;
var rectTemplate = null;
var grabPoint = null;
var clientPoint = null;
var rectX = null;
var rectY = null;
var rectWidth = null;
var rectHeight = null;
var arr=new Array();
var resizingLeft = false;
var resizingRight = false;
var rectrot=null
ob.addEventListener("load", function(){
svgDoc=ob.contentDocument;
svgRoot=svgDoc.documentElement;
grabPoint = svgRoot.createSVGPoint();
clientPoint = svgRoot.createSVGPoint();
rectTemplate = svgDoc.getElementById('rectTemplate')
rectrot=svgDoc.getElementById("rect1")
}, false)
var angel=0
function Rotate1()
{
angel=angel+10
//alert(rectrot)
var c=rectTemplate.getAttribute("transform");
var widt=Number(rectTemplate.getAttribute("width"))/2;
var hie=Number(rectTemplate.getAttribute("height"))/2
var tran=c.match(/[\d\.]+/g);
var newxpo=Number(tran[0])+widt;
var newypo=Number(tran[1])+hie;
var r=Math.tan((newxpo)/(newypo))
rectTemplate.parentNode.setAttribute("transform","translate("+newxpo+" "+newypo+")"+"rotate("+angel+") translate("+(newxpo*-1)+" "+(newypo*-1)+")");
}
function MouseDown(evt)
{
var targetElement = evt.target;
var checkForResizeAttempt = false;
if (targetElement == rectTemplate)
{
//arr.push(cir ,cir1,rectTemplate)
dragTarget = targetElement;
checkForResizeAttempt = true;
var transMatrix = dragTarget.getCTM();
grabPoint.x = evt.clientX - Number(transMatrix.e);
grabPoint.y = evt.clientY - Number(transMatrix.f);
}
var transMatrix = dragTarget.getCTM();
//var transMatrix = dragTarget.getCTM().inverse();
grabPoint.x = evt.clientX - Number(transMatrix.e);
grabPoint.y = evt.clientY - Number(transMatrix.f);
if (window.console) console.log(grabPoint.x + " " + grabPoint.y);
if (window.console) console.log(evt.clientX + " " + evt.clientY);
if (checkForResizeAttempt)
{
clientPoint.x = evt.clientX;
clientPoint.y = evt.clientY;
rectX = Number(dragTarget.getAttributeNS(null, "x"));
rectY = Number(dragTarget.getAttributeNS(null, "y"));
rectWidth = Number(dragTarget.getAttributeNS(null, "width"));
rectHeight = Number(dragTarget.getAttributeNS(null, "height"));
if ((grabPoint.x - rectX) < 10)
{
resizingLeft = true;
}
else if (((rectX + rectWidth) - grabPoint.x) < 10)
{
resizingRight = true;
}
if (resizingLeft || resizingRight)
{
dragTarget.setAttributeNS(null,"stroke","green");
}
else
{
dragTarget.setAttributeNS(null,"stroke","black");
}
}
}
function MouseMove(evt)
{
evt.stopPropagation();
if (dragTarget == null)
{
return;
}
if (resizingLeft)
{
if (window.console) console.log(evt.clientX + " " + evt.clientY);
deltaX = (clientPoint.x - evt.clientX);
if (window.console) console.log("deltaX = " + deltaX);
dragTarget.setAttributeNS(null,"width",rectWidth + deltaX);
dragTarget.setAttributeNS(null,"x",rectX - deltaX);
}
else if (resizingRight)
{
deltaX = (clientPoint.x - evt.clientX);
if (window.console) console.log("rectWidth = " + rectWidth + " deltaX = " + deltaX);
dragTarget.setAttributeNS(null,"width",rectWidth - deltaX);
}
else
{
var newXX = evt.clientX-grabPoint.x;
var newYX = evt.clientY-grabPoint.y;
dragTarget.setAttributeNS(null,'transform','translate(' + newXX + ',' + newYX + ')');
}
}
function MouseUp(evt)
{
evt.stopPropagation();
if (dragTarget == null)
{
return;
}
resizingLeft = false;
resizingRight = false;
resizingTop = false;
resizingBottom = false;
// var transMatrix = dragTarget.getCTM().inverse();
dragTarget.setAttributeNS(null,"stroke","blue");
dragTarget = null;
}
</script>
</body>
</html>
--
=======SVG ====
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="612px" height="792px" xml:space="preserve"
onmousedown="ecmascript:top.MouseDown(evt)"
onmousemove="ecmascript:top.MouseMove(evt)"
onmouseup="ecmascript:top.MouseUp(evt)">
<g id="rect1">
<rect id="rectTemplate" x="0" y="0" stroke="blue" width="100" height="30" />
</g>
I have posted a sample of dragging and resizing transformed SVG rects in my answer here:
SVG coordinates with transform matrix
You can see the working example on my site here:
http://phrogz.net/svg/drag_under_transformation.xhtml
The key is to:
When you start dragging (mousedown) record the mouse location (in SVG global space).
During dragging (mousemove) calculate the offset (in SVG global space) for the drag, and then
Transform that offset from global space into the local space of the object, and use that to inform your changes.
This works regardless of the transformation hierarchy applied (as shown in my example).
Have you tried to change your code to rotate the shape around the center of the shape?
Here is an excerpt of the W3C draft on transform:
rotate(<rotate-angle> [<cx> <cy>]),
which specifies a rotation by <rotate-angle> degrees about a given point.
If optional parameters <cx> and <cy> are not supplied, the rotate is about the origin of the current user coordinate system.
The operation corresponds to the matrix [cos(a) sin(a) -sin(a) cos(a) 0 0].
If optional parameters <cx> and <cy> are supplied, the rotate is about the point (cx, cy).
The operation represents the equivalent of the following specification:
translate(<cx>, <cy>) rotate(<rotate-angle>) translate(-<cx>, -<cy>).
If you set cx and cy to the center of your ribbon, this may help from what context I can pick up from your code.