svg drag

469 views
Skip to first unread message

Erkal Selman

unread,
Mar 3, 2015, 11:40:00 AM3/3/15
to elm-d...@googlegroups.com
Hi,

I am new to elm and already a fan of it.
  1. Look at this example from mercury. (Here is the source.) Is it easy to do this in elm?
  2. Are there any examples for events in elm-svg? The source code looks very clean. I am sure that it is possible to read and understand it, but for beginners like me, examples would be very helpful.
Thanks in advance.

Evan Czaplicki

unread,
Mar 4, 2015, 12:46:41 PM3/4/15
to elm-d...@googlegroups.com
Hey, sorry for the delay!

It looks like this would be relatively easy to do in Elm, but the examples for elm-svg are unfortunately meager at the moment! I would recommend checking out elm-todomvc. It uses an architecture, described here in depth, that I would definitely apply directly to any SVG programming I was doing.

So I'd say, take a look at the architecture tutorial, then look to the todomvc for a larger version. The main difference will that be the Html parts will be Svg parts. I would really like to get you better examples though, so maybe I can rustle up some people from the community to do some examples!

P.S. Other folks on the list, this is maybe the most important kind of question on the list. These are the questions that will lead to success or failure, not speculative design discussions!!!

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Kaspar Emanuel

unread,
Mar 4, 2015, 4:04:10 PM3/4/15
to elm-d...@googlegroups.com

On 4 March 2015 at 17:46, Evan Czaplicki <eva...@gmail.com> wrote:
P.S. Other folks on the list, this is maybe the most important kind of question on the list. These are the questions that will lead to success or failure, not speculative design discussions!!!

Can't it be both? ;p

Evan Czaplicki

unread,
Mar 4, 2015, 4:08:53 PM3/4/15
to elm-d...@googlegroups.com
Kaspar, yes, of course! You don't make a language like Elm if you don't care about theory and design. I'm just pointing out that we need a balance. 40 comments on theory and 0 on examples is not a good balance.

--

Kaspar Emanuel

unread,
Mar 4, 2015, 4:27:03 PM3/4/15
to elm-d...@googlegroups.com

On 4 March 2015 at 21:08, Evan Czaplicki <eva...@gmail.com> wrote:
Kaspar, yes, of course! You don't make a language like Elm if you don't care about theory and design. I'm just pointing out that we need a balance. 40 comments on theory and 0 on examples is not a good balance.

Yes I know, I was just joking around. It's easy to get obsessed with theory.

Erkal Selman

unread,
Mar 4, 2015, 5:14:43 PM3/4/15
to elm-d...@googlegroups.com
I cannot run the architecture tutorial.
If I click on Counter.elm, I get the following error message:

Error when searching for modules imported by module 'Counter':
   Could not find module 'Html'

 


--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/6nv1KkBs1a8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Evan Czaplicki

unread,
Mar 4, 2015, 5:23:57 PM3/4/15
to elm-d...@googlegroups.com
Are you running elm-reactor in the root of the project, or in the directory 1/ ? Because I use the same module names in all the examples, it's necessary to run elm-reactor from 1/ 2/ 3/ and 4/ not from the root!

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Martin Verzilli

unread,
Mar 4, 2015, 5:25:46 PM3/4/15
to elm-d...@googlegroups.com, elm-d...@googlegroups.com
Hi Erkal! I stumbled upon the same issue when trying to run that tutorial. 

Apparently in my case it was because I had an older version of Elm, because downloading the latest from http://elm-lang.org/Install.elm
and upgrading made the issue go away.



Hassan Hayat

unread,
Mar 4, 2015, 9:41:41 PM3/4/15
to elm-d...@googlegroups.com
Enter code here...

Hi Erkal,

So, your example is very interesting and I'd like to share a solution of how to implement it. The following code is what you get when you just implement it (without trying to make it more extensible or anything). So, I'll let you judge of how easy it is to implement. I'm quite used to Elm so this is really easy for me, so I'm the wrong person to ask perhaps.

So, first and foremost, the preliminaries. Obviously, I use elm-svg and elm-html cuz they're necessary to get the thing up and running. Furthermore, I used Janis's drag and drop library which is amazingly easy to use: http://package.elm-lang.org/packages/jvoigtlaender/elm-drag-and-drop/1.0.1

In the end, my imports look like this:

import Svg (Svg, circle, svg, g, line)
import Svg.Attributes (cx, cy, r, fill, stroke, strokeWidth, x1, x2, y1, y2)
import Html (Html)
import Html.Attributes (style)

import Signal (Signal, map, foldp)
import DragAndDrop (..)
import List


 
Now to the code!

We're gonna be dealing around a lot in 2d space, so we need vectors. 

type alias Vector = (Int, Int)

For simplicity, I'll be dealing with ints the whole time, but you could just change everything to Floats if you want. Just call "toFloat" in the right places and you'll be good.


When I approached the problem, I initially thought of using svg events (and onMouseDown). The problem with this is that onMouseDown does not give you the mouse position. Now, you could make it work with mouse positions, but the drag and drop library is really easy to use. This means that I have to deal with the selection of the points to drag manually. In doing so, I need a way to keep track of which point I have selected when I am dragging. As such, each point is not just of type Vector as that type does not contain enough information. I created another type called Point to store that additonal information:

type alias Point =
  { position : Vector
  , selected : Bool
  , radius   : Int
  }


This represent the points you see on the screen. A point has a position (which is a vector), it has a selected state (True or False) and has a radius (useful for drawing and detecting the clicks). 

From this type, I can then create a triangle type to model the triangle:

type alias Triangle = (Point, Point, Point)

A triangle is simply a tuple of 3 points (cuz a triangle has 3 points).

The weirder type is the circle. If you notice in the example, the circle is composed of two points (one at the center and one at the edge). 

So, we can simply model a circle to be a tuple of 2 points:

type alias Circle = (Point, Point)

The first one is the center and the second one is the edge. I'd like to note that if you were to actually make a 2d drawing library, the better way would be to make a center have a center and a radius, but this'll do for this example.

I can now model the state of the program. I decided to model the state as having a list of triangles and a list of circles (it can be just one triangle and circle, but I'm doing this on purpose so that you may add additional triangles and circles as you play with the code):

type alias State =
  { triangles : List Triangle
  , circles   : List Circle
  }


So, this is the entire state of the program fully encapsulated in this type.


Now, off to Rendering!

Basically, for rendering, you'd like a function that has the following type signature :

view : Model -> Html

(If you look in the architecture tutorial, the elm architecture allows for more complex view functions but this will do for now cuz it's super simple).

Basically, this function just takes a model and displays it. Simple, no?

This is how I implemented it:

view : State -> Html
view {circles, triangles} =
  svg
    [ style
      [ ("border", "1px solid black")
      , ("width", "800px")
      , ("height", "600px")]
    ]
    ((List.map drawCircle circles) ++ (List.map drawTriangle triangles))

So, let me walk you through this code.

"view" takes a Model and returns an Html. I capture the fields of the model through pattern matching (so, circles is the the list of circles in the model and triangles is the list of triangles in the model)

I then create an svg node.

"svg" has the following type signature:

svg : List Html.Attribute -> List Svg -> List Html.Html

The first list it takes is a list of html attributes. In this case I give it just one attribute: a style. This is how you get inline styles in elm-html. style takes a list of tuples of 2 strings. The first string is the name of the css property and the second is the value you wish to set to the css property. As you can see, I'm more or less mirroring the css styles in the example by giving the whole svg block a width, a height, and a border.

Then comes the list of svg nodes. This comes from this statement:

((List.map drawCircle circles) ++ (List.map drawTriangle triangles))

What this says is : append the result of mapping "drawCircle" to the circles in the model to the result of mapping "drawTriangle" to the triangles in the model. In essence: draw all the circles and all the triangles.

So, now let's see each of these functions individually:

First, drawCircle:

drawCircle : Circle -> Svg
drawCircle (center, edge) =
  let
      (x1', y1') = center.position
      radius = distance center.position edge.position
  in
    g
      []
      [ drawPoint center
      , drawPoint edge
      , drawLine center.position edge.position
      , circle
          [ cx (toString x1')
          , cy (toString y1')
          , r  (toString radius)
          , fill "rgba(255,0,0,0.1)"
          , stroke "black"
          , strokeWidth "2"
          ]
          []
      ]


So, drawCircle takes a circle and returns an Svg node. Again I use some pattern matching for convenience to capture the center of the circle and the edge point. 
I then alias the x and y values of the center's position as x1' and x2' respectively (you'll see why later) and I also get the radius of the circle from the distance between the center's position and the edge's position. I defined the distance function separately as follows:

distanceSquared : Vector -> Vector -> Int
distanceSquared (ax, ay) (bx, by) =
  (ax - bx) * (ax - bx) + (ay - by) * (ay - by)

distance : Vector -> Vector -> Int
distance p q =
  round (sqrt (toFloat (distanceSquared p q)))


This is super simple pythagoras with conversions to and from floats cuz I want an Int as a result.

Now, to the meat of the drawCircle function.

We create a svg group with "g" (just like in regular svg), we give it no attributes (the empty list) and then we give it a bunch of svg nodes. If you look at the circle in your example, the circle consists of those two points (the center and the edge), the pinkish circle and the black line between the two points. So, we draw each point (the drawPoint function does that), then we draw the line between the two points (the drawLine function) and then we draw the circle, which you can see the whole details. If you're familiar with svg this should have no secret for you. The circle itself will have no inner svg but will have tons of attirbutes. "cx" is for the x position of the center, "cy" is for the y position, r is the radius, fill is the color, stroke is the stroke color around the circle and the strokeWidth is just the stroke width. All these attributes take strings so conversions to string are make where necessary with the toString function.


If you understand how drawCircle works, you'll have no problem understanding the other functions. Here's the full code for rendering here for you to look at. It's all just more of the same and should be self-explanatory:

drawPoint : Point -> Svg
drawPoint {position, radius} =
  let
      (x,y) = position
  in
    circle
      [ cx (toString x)
      , cy (toString y)
      , r (toString radius)
      , fill "rgba(0, 0, 255, 1)"
      ]
      []


drawLine : Vector -> Vector -> Svg
drawLine (x1', y1') (x2', y2') =
  line
    [ stroke "black"
    , strokeWidth "2"
    , x1 (toString x1')
    , x2 (toString x2')
    , y1 (toString y1')
    , y2 (toString y2')
    ]
    []

drawCircle : Circle -> Svg
drawCircle (center, edge) =
  let
      (x1', y1') = center.position
      radius = distance center.position edge.position
  in
    g
      []
      [ drawPoint center
      , drawPoint edge
      , drawLine center.position edge.position
      , circle
          [ cx (toString x1')
          , cy (toString y1')
          , r  (toString radius)
          , fill "rgba(255,0,0,0.1)"
          , stroke "black"
          , strokeWidth "2"
          ]
          []
      ]


drawTriangle : Triangle -> Svg
drawTriangle (a,b,c) =
  g
    []
    [ drawPoint a
    , drawPoint b
    , drawPoint c
    , drawLine a.position b.position
    , drawLine b.position c.position
    , drawLine c.position a.position
    ]



view : State -> Html
view {circles, triangles} =
  svg
    [ style
      [ ("border", "1px solid black")
      , ("width", "800px")
      , ("height", "600px")]
    ]
    ((List.map drawCircle circles) ++ (List.map drawTriangle triangles))


And that's it for rendering. 

So, now, off to updating. 

We'd like a way to define a transition from one state to the next given some input. Our only input here is MouseEvent (from the drag and drop library), so for simplicity I'll just use that as the input but I refer you to the elm architecture for ways to make this more modular.

I call this transition function step and it will have the following signature:

step : MouseEvent -> State -> State

step takes the input (in this case the MouseEvent) and it takes the old state and produces a new state.

Let's look at how "step" works:

step : MouseEvent -> State -> State
step mouseEvent state =
  { state | circles <- List.map (stepCircle mouseEvent) state.circles
          , triangles <- List.map (stepTriangle mouseEvent) state.triangles
  }

So, step just updates the state via record updates. We update the circles to be the mapping of stepCircle on the circles and we update the triangles to be the mapping of stepTriangle on the circles. Basically, we just step all the circles and all the triangles. (each of these functions take a mouseEvent as a parameter)

Let's look at each of those functions:

stepTriangle : MouseEvent -> Triangle -> Triangle
stepTriangle mouseEvent (a,b,c) =
  ( stepPoint mouseEvent a
  , stepPoint mouseEvent b
  , stepPoint mouseEvent c
  )


stepCircle : MouseEvent -> Circle -> Circle
stepCircle mouseEvent (center, edge) =
  ( stepPoint mouseEvent center
  , stepPoint mouseEvent edge
  )

They're each quite simple. In both cases we update all of their points. So, really the magic happens in "stepPoint". Let's look at it.

stepPoint : MouseEvent -> Point -> Point
stepPoint mouseEvent point = case mouseEvent of
  StartAt start ->
    if
      start `within` point
    then
      { point | selected <- True
              , position <- start
      }
    else
      { point | selected <- False }

  MoveFromTo origin destination ->
    if
      point.selected
    then
      { point | position <- destination }
    else
      point

  EndAt destination ->
    if
      point.selected
    then
      { point | position <- destination
              , selected <- False
      }
    else
      point


Woah, that's quite some code there, so lets walk through this carefully. First, let me explain how the drag and drop library works. 

The basic type you work with in the drag and drop library is the "MouseEvent" type. This type is a union type defined as follows:

type MouseEvent
= StartAt (Int,Int)
| MoveFromTo (Int,Int) (Int,Int)
| EndAt (Int,Int)

So, a mouse event is either 
  1) the first click (StartAt) where you get the (x,y) coordinates of the mouse
  2) a mouse move from some (x,y) to some (x',y')
  3) the position (x,y) of the mouse when you released the mouse click (EndAt)


So, not much to it. But this means that we're going to have to handle each case separately.This is where we have "case mouseEvent of". So, let's look at each case:

Case 1) StartAt

StartAt start ->
  if
    start `within` point
  then
    { point | selected <- True
            , position <- start
    }
  else
    { point | selected <- False }

If we have a StartAt, then we check is the start position of the mouse (which I call "start") is within the point. If it is, we set the selected field of the point to true (because the mouse is inside the point) and we set the position of the point to start. Else, we just do nothing (I set the selected to False as a defensive measure against bugs, but this is normally not necessary).

What's this "within" function?

within : Vector -> Point -> Bool
within vector point =
  distanceSquared vector point.position <= point.radius * point.radius

It's just standard checking if a point is inside a circle. A point is inside a circle if the distance between the point and the center of the circle is less than the radius of the circle. If you ever do some game programming, you'll appreciate the use of distanceSquared instead of distance to save on using the "sqrt" function, but that doesn't change the correctness of the code.


Case 2) MoveFromTo


MoveFromTo origin destination ->
  if
    point.selected
  then
    { point | position <- destination }
  else
    point

If the point is selected, we move it to the destination. Else we don't do anything to the point. This one is pretty straightforward.


Case 3) EndAt

  EndAt destination ->
    if
      point.selected
    then
      { point | position <- destination
              , selected <- False
      }
    else
      point


If the point is selected, we set the position the destination (where the click was released) and we remember to set the selected field to false. Else, we do nothing. Again, this is relatively straightforward.

And, that's it for the update!

So, we have our model. We have how to render the model. We have how to update the model. Let's put all this together:

main : Signal Html
main =
  map view
    (foldp step initialState mouseEvents)

This is our main function. Let's parse what this says slowly.

First we map "view" onto something. This something is "foldp" of the step function taking some initial state and mouseEvents.

"foldp" is fold from the past. It allows you to transition from the present to the future. "mouseEvents" comes from the drag and drop library and is a signal of those MouseEvents we talked about. Everytime you click and drag on the page, "mouseEvents" will trigger a new "MouseEvent". This new MouseEvent will get fed into the step function and update the current state of the program. "initialState" is the initial state of the program or how the program looks like when it is first run.

"initialState" is defined as follows:

initialState : State
initialState =
  { triangles = [ initialTriangle ]
  , circles   = [ initialCircle ]
  }

Where we have our triangle and our circle. These are defined in a way to mirror the example: 

initialTriangle : Triangle
initialTriangle =
  (point 100 100, point 200 200, point 100 200)

initialCircle : Circle
initialCircle =
  (point 250 300, point 250 250)


where point is just a helper function:

point : Int -> Int -> Point
point x y =
  { position = (x,y)
  , selected = False
  , radius   = 5
  }


So, "foldp" gives us a signal of State where each state is updated via the "step" function and is fed the input from "mouseEvents". And then we map "view" onto that to render a new view each time the state changes. And that's it. 


Here's the whole code for a big family photo. You're free to do whatever you want with it and are welcome to play around with it. Please do not hesitate in asking any questions. I hope that this is helpful. 

import Svg (Svg, circle, svg, g, line)
import Svg.Attributes (cx, cy, r, fill, stroke, strokeWidth, x1, x2, y1, y2)
import Html (Html)
import Html.Attributes (style)

import Signal (Signal, map, foldp)
import DragAndDrop (..)
import List

-------------
-- Vector Type
-------------
type alias Vector = (Int, Int)


distanceSquared : Vector -> Vector -> Int
distanceSquared (ax, ay) (bx, by) =
  (ax - bx) * (ax - bx) + (ay - by) * (ay - by)

distance : Vector -> Vector -> Int
distance p q =
  round (sqrt (toFloat (distanceSquared p q)))

--------
-- Model
--------

type alias Point =
  { position : Vector
  , selected : Bool
  , radius   : Int
  }

within : Vector -> Point -> Bool
within vector point =
  distanceSquared vector point.position <= point.radius * point.radius

type alias Triangle = (Point, Point, Point)

type alias Circle = (Point, Point)

type alias State =
  { triangles : List Triangle
  , circles   : List Circle
  }

point : Int -> Int -> Point
point x y =
  { position = (x,y)
  , selected = False
  , radius   = 5
  }


initialTriangle : Triangle
initialTriangle =
  (point 100 100, point 200 200, point 100 200)

initialCircle : Circle
initialCircle =
  (point 250 300, point 250 250)


initialState : State
initialState =
  { triangles = [ initialTriangle ]
  , circles   = [ initialCircle ]
  }

-------
-- VIEW
-------

drawPoint : Point -> Svg
drawPoint {position, radius} =
  let
      (x,y) = position
  in
    circle
      [ cx (toString x)
      , cy (toString y)
      , r (toString radius)
      , fill "rgba(0, 0, 255, 1)"
      ]
      []


drawLine : Vector -> Vector -> Svg
drawLine (x1', y1') (x2', y2') =
  line
    [ stroke "black"
    , strokeWidth "2"
    , x1 (toString x1')
    , x2 (toString x2')
    , y1 (toString y1')
    , y2 (toString y2')
    ]
    []

drawCircle : Circle -> Svg
drawCircle (center, edge) =
  let
      (x1', y1') = center.position
      radius = distance center.position edge.position
  in
    g
      []
      [ drawPoint center
      , drawPoint edge
      , drawLine center.position edge.position
      , circle
          [ cx (toString x1')
          , cy (toString y1')
          , r  (toString radius)
          , fill "rgba(255,0,0,0.1)"
          , stroke "black"
          , strokeWidth "2"
          ]
          []
      ]


drawTriangle : Triangle -> Svg
drawTriangle (a,b,c) =
  g
    []
    [ drawPoint a
    , drawPoint b
    , drawPoint c
    , drawLine a.position b.position
    , drawLine b.position c.position
    , drawLine c.position a.position
    ]



view : State -> Html
view {circles, triangles} =
  svg
    [ style
      [ ("border", "1px solid black")
      , ("width", "800px")
      , ("height", "600px")]
    ]
    ((List.map drawCircle circles) ++ (List.map drawTriangle triangles))


---------
-- UPDATE
---------

stepPoint : MouseEvent -> Point -> Point
stepPoint mouseEvent point = case mouseEvent of
  StartAt start ->
    if
      start `within` point
    then
      { point | selected <- True
              , position <- start
      }
    else
      { point | selected <- False }

  MoveFromTo origin destination ->
    if
      point.selected
    then
      { point | position <- destination }
    else
      point

  EndAt destination ->
    if
      point.selected
    then
      { point | position <- destination
              , selected <- False
      }
    else
      point


stepTriangle : MouseEvent -> Triangle -> Triangle
stepTriangle mouseEvent (a,b,c) =
  ( stepPoint mouseEvent a
  , stepPoint mouseEvent b
  , stepPoint mouseEvent c
  )


stepCircle : MouseEvent -> Circle -> Circle
stepCircle mouseEvent (center, edge) =
  ( stepPoint mouseEvent center
  , stepPoint mouseEvent edge
  )

step : MouseEvent -> State -> State
step mouseEvent state =
  { state | circles <- List.map (stepCircle mouseEvent) state.circles
          , triangles <- List.map (stepTriangle mouseEvent) state.triangles
  }

-------
-- MAIN
-------

main : Signal Html
main =
  map view
    (foldp step initialState mouseEvents)



Hassan Hayat

unread,
Mar 4, 2015, 10:14:31 PM3/4/15
to elm-d...@googlegroups.com
Oh, and Erkal, about the running of Elm, when I first started to use Elm I encountered a few difficulties getting set up especially since I don't know much about the command line. I'm on Mac OS but these tips are more or less valid for Windows. Please disregard the two thirds of these instructions if you're familiar with the command line and if you're on Windows, I'm sorry but I'm not familiar with the platform and I hope that the instructions can still help.

1) Make sure you use the installer : http://elm-lang.org/Install.elm 
This is by far the easier way to use elm. It comes pre-packaged with everything "elm-reactor", the package manager, the compiler, etc...

2) Make a folder for your project (I usually put this folder in another folder called "Elm" or something)

3) Create your Elm files in the project (and work on your project). To make sure everything works, write this code:

import Text

main
= Text.asText "Hello World"

I do this all the time to make sure that I'm set up

4) Open the terminal. Type "cd" followed by a space and then drag the folder on the terminal and press enter.

5) In the terminal, type "elm-reactor". This should start a local server at port 8000

6) Open your favorite browser and go to http://localhost:8000/

7) Navigate the file menu there until you get to your file and open it. 

8) Hopefully you should see "Hello world" on the top left.



Now, a word on libraries. The easiest way to install a library is via the terminal. So, say you want to install "elm-html".

1) Go back to the terminal, and exit the elm-reactor by doing ctrl+c (or alternatively you can just open a new tab and make sure your terminal points to the project folder, if it doesn't, type cd followed by a space, drag your folder in the terminal and press enter)

2) type "elm-package install" followed by a space and then followed by the name of the package. In this case, the full name of the package is "evancz/elm-html". As you may notice, each package is namespaced to the github name of the library author. So the full command would be "elm-package install evancz/elm-html". 

3) Just say yes to the stuff it asks you (you say yes by typing "y" and pressing enter)

4) Now you have added "elm-html" to your project. To check that you have used it, go back to your file and add this line to your imports

import Html

5) Restart elm-reactor and re-open your file in the browser. If all is well you should still get "hello world".


You'll have to do this for all the libraries you import, the only that gets imported automatically is the core library. You might notice that this process creates a file called elm-package.json. This is where the info regarding the libraries reside, so, make sure you don't delete that file. 

This is how I usually get set up to work on Elm. I hope it helps. Given the error you get, I'm imagining that this may be due to the fact that you haven't downloaded elm-html or elm-svg. Follow the above steps to do so. If this wasn't your problem, please ask again and detail the steps of what you have done, the system you are in, etc... Only with that level of detail can we help you effectively.


On Tuesday, March 3, 2015 at 4:40:00 PM UTC, Erkal Selman wrote:

Hassan Hayat

unread,
Mar 4, 2015, 11:23:44 PM3/4/15
to elm-d...@googlegroups.com
I put the example in a github gist so you can see the code with nice syntax highlighting and stuff

https://gist.github.com/TheSeamau5/8847c0e8781a3e284d82
...

Erkal Selman

unread,
Mar 5, 2015, 2:20:31 PM3/5/15
to elm-d...@googlegroups.com

Thank you Evan. Running elm-reactor inside folder /1 solved my problem.

Wow, Hassan I am impressed with the detailedness of your answer.
Thank you very much.

I have further questions, but first,
I think that I need to explain why I asked this question.

I studied mathematics and I am working on logic and graph theory.
I don’t have much experience with programming languages
but it is not that difficult for me to understand purely functional languages.
Their source code is very near to how we write constructive proofs.

Graph theorists do not have good software for
drawing example graphs,
coloring and manipulating them,
partitioning the vertex-set,
visualizing steps of algorithms,
etc.

People have done some nice things like
visualgo.net
but what we need is something more powerful and open source.

Users (which will be mostly graph theorists)
should be able to construct graphs by writing algorithms.
They should be able
to download the entire program from github and easily extend the code.
So, together with the graph drawing tool,
we need a nice programming language
For sure, javascript is not an option.
Whenever mathematicians or computer scientists hear the word ‘javascript’
their faces are like
these.

I started building such a tool using plain javascript and D3.js but as my GUI got bigger, everything got messy.
So I gave up implementing a GUI.
I decided to let the user manipulate the graphs using the console of the browser.

Implementing “undo” is also a must for such a tool.
This is not easy without persistent data structures.
So, I should use those libraries of Facebook.
But I don’t want to learn thousand things only because of the shortcomings of javascript.

At the end, I realised that I need a good programming language to create this tool.
I do not want to deal with the problems of javascript.

I saw some videos of Evan
and I’ve got the impression that in elm,
visualising algorithms step by step is so simple
that it is almost like a built-in language feature.
So is undo.
My interest shifted from ClojureScript to elm.
(Of course this is not the only reason. In many aspects, elm seems to be the dream language for my purposes.)

Related to svgdragdrop.elm :

  1. Here is, why I think that using svg onMouseDown
    instead of using elm-drag-and-drop
    may be a better idea:

    Every time the user clicks somewhere, you run over all the points to look for
    whether that point is clicked.
    Also during the drag, you run over all points.
    If you have many points, this can cause a performance problem.
    Here
    is an example with 50 x 50 points.
    Just download or clone the repo.
    (I used your code to create this.)
    If you drag a point slowly you will notice the problem.

    But the biggest problem is the following.
    What if, for example, the svg-elements are not circles but rectangles or worse, svg-paths.
    It can get very difficult to find out, which element the user clicked on.

    Another problem is that if you have, for example, two big overlapping circles
    and you click on the area where they overlap,
    you drag both.
    The expected behavior would be dragging only the one which is on top.

  2. Another issue is the “beaming” on mouseDown.
    If you don’t click on the center of the element, the element jumps.
    But solving this problem should be easy,
    once you get the “center” (I mean the anchor-point) of the svg-element.
    See
    here
    to see how Mike Bostock solved it for D3.

  3. In a language like elm,
    it should be possible to create a solution
    which is at least as clean as in
    this .
    What I would like to have, is some function like
    makeDraggable : Svg -> Svg
    or
    makeDraggable : (Int, Int) -> Svg -> Svg
    where (Int, Int) is used as the .origin
    in Mike Bostock’s solution for the “beaming problem”.

  4. I think that elm can a better alternative to D3 for visualising data.
    But there is a lot to be done.

Thanks again for the help (and in the first place for creating elm).

Hassan Hayat

unread,
Mar 5, 2015, 3:25:41 PM3/5/15
to elm-d...@googlegroups.com
I'm glad you found the answer useful.

So, first of all, about the performance, sure, the performance isn't there with the example. I thought that you just wanted to see how an implementation would look like and judge if the code that comes out of it looks easy. I agree, there is onMouseDown and we shouldn't be doing collision detection for this sort of thing. About the performance of crawling for lists, a better data structure would be Array which has better characteristics. But even better would be a quadtree. That's what games actually do and with a grid of 50 x 50 points, you can imagine finding the appropriate point(s) in just a couple of steps. But that's besides the point. I'll try and see if I can whip up something that uses onMouseDown and see if it can help.

Now, to the graph theory. I think you'll find loads of people interested in representing graphs visually in this list.

For example, check out these projects that are "up for grabs" in Elm https://github.com/elm-lang/projects. You'll see that some are called "visualize compilation" and "visualize tests". You can add "visualize signals" to that list if you want. Signals, in Elm, are just acyclic graphs. You get an input from the world, like the mouse position. Then that mouse position gets fed into the step function, which gets fed into the individual step functions, which, etc...., until everything that has become a signal gets updated and then you repeat at the next input. Visualizing signals is interesting for debugging. One could also imagine visualizing a program as a graph (as in flow-based programming).


Elm is built for precisely this sort of visualizations. You should be able to visualize the past and future states in an algorithm as you tweak a small parameter. For example, imagine visualizing dijkstra's algorithm for a weighted graph. Well, you could just easily tweak one of the weights and see the results at the step you are currently visualizing or at previous or future steps. 

Plus, you can go nuts with this graphs idea. The elm architecture is a unidirectional graph. It's a cycle but it doesn't really branch out or anything. That can be represented as well. Pattern matching is really just a state machine in disguise. State machines are graphs. You get where I'm going with this.

But obviously, there needs more work on this front for Elm to be able to represent graphs well. First of all, we need a solid matrix library with all the factorizations and the eigenvalues and the exponentiation and all that jazz. Elm also needs (which it's getting soon) better Ajax support and more facilities for sending data via websockets and stuff. For example, say you are a mathematician and would like to visualize on the web your super duper awesome new edge coloring algorithm. But to show its usefulness, you'd like to run it on millions of nodes and you've got your code written in C++ or Julia or Mathematica or whatever running somewhere in some cloud and you'd like to get that data to visualize on the browser (the rendering done client-side). So, for this, Elm would need more libraries to consume more kinds of data structures and, of course, a good library for visualizing graphs. 


From all this, what I meant to say is that there's a lot of interest in visualizing graphs in Elm, so you'd be in good company. :D
...

Erkal Selman

unread,
Mar 5, 2015, 5:11:21 PM3/5/15
to elm-d...@googlegroups.com

Erkal Selman

unread,
Mar 5, 2015, 5:15:38 PM3/5/15
to elm-d...@googlegroups.com

Hassan Hayat

unread,
Mar 6, 2015, 3:03:46 PM3/6/15
to elm-d...@googlegroups.com
So, I've been looking at this question for a bit and there are a few things that I've learned from it.

1) On the idea of performance, it's nearly impossible to have one point in the model update on an event without doing some sort of traversal. This is an unfortunate consequence of having the step function that looks like this:

step : Input -> Model -> Model
 

So, at some point you have to look at that input and decide what to do with the entire model. So, the traversal must be optimized by the selection of an appropriate data structure (k-d trees, quadtrees, arrays, dicts, etc...)

2) On the problem of using the events "onMouseDown" and so on... The problem with "onMouseDown" is that it has the following type:

onMouseDown : Signal.Message -> Attribute
 
 
We don't capture the Mouse data from this unfortunately. So, it's tedious to have to merge the signal with Mouse.position and stuff and then convert that to an action... yuck.

Ideally, we'd like "onMouseDown" to have the following type:

onMouseDown : (MouseEvent -> Signal.Message) -> Attribute
 

and it turns out that I have made such a pull request on elm-html (elm-svg works the same, so if the PR goes through, a bit of copy-pasting will happen and you'll get the same on elm-svg)

The MouseEvent contains stuff like clientX, clientY, and also which mouse button got clicked. So you can detect if the right mouse button was clicked or the left mouse button. That'll make things way easier to deal with. 

Once we have that, we can define stuff like onMouseDrag in terms of onMouseDown, onMouseMove and onMouseUp (cuz we'd have all the data we need) just like how d3 does.

Then, the idea of not having the element jump when selected can be made into a function and the whole thing can then be turned into a library like the drag and drop library. 


So, by using onMouseDown, as you pointed out, we could capture arbitrarily complex svg elements. But then comes the problem of drilling down the data structure for that specific element without having to actually do collision detection. The solution would be to have each point have an ID and put your points in Dicts. You can then send the ID along with the message. You can imagine your onMouseDown call to look like this:

drawPoint point =
  circle
 
[ cx (toString point.x)
 
, cy (toString point.y)
 
, r  "5"
 
, onMouseDown (\{clientX, clientY} -> send mouseDrags (StartDrag point.id (clientX, clientY)))
 
] []
 

Where you can imagine your mouseDrags to look something like this:

type MouseDrag
 
= StartDrag ID (Int, Int)
  | DragFromTo ID (Int, Int) (Int, Int)
 
| EndDrag ID (Int, Int)
 
| NoDrag

mouseDrags
: Channel MouseDrag
mouseDrags
= channel NoDrag
 


So, you can imagine capturing all your points (or all your shapes) in one large dictionary:

type alias Graphic = Dict ID Shape

Then, obviously, the library would take care of making it easy to work with. 

So, I'm sorry that I don't have a satisfying answer but hopefully soon enough the changes I proposed will happen in some form and then I can give you a more satisfying answer. 
...
Reply all
Reply to author
Forward
0 new messages