gampleman / elm-visualization Goto Github PK
View Code? Open in Web Editor NEWA data visualization library for Elm
Home Page: http://package.elm-lang.org/packages/gampleman/elm-visualization/latest/
License: MIT License
A data visualization library for Elm
Home Page: http://package.elm-lang.org/packages/gampleman/elm-visualization/latest/
License: MIT License
Currently, when constructing scales, the arguments are domain
and then range
. This is somewhat intuitive, but in practice the range is often known statically, whereas the domain is computed from the data.
So currently you can see code like this:
width : Float
width = 800
makeXScale : (Float, Float) -> ContinuousScale
makeXScale domain =
Scale.linear domain (0, width)
makeColorScale : (Float, Float) -> SequentialScale Color
makeColorScale domain =
Scale.sequential domain Scale.viridisInterpolator
whereas if the arguments were reversed, the code would simplify to:
width : Float
width = 800
makeXScale : (Float, Float) -> ContinuousScale
makeXScale = Scale.linear (0, width)
makeColorScale : (Float, Float) -> SequentialScale Color
makeColorScale = Scale.sequential Scale.viridisInterpolator
Thoughts?
Hello,
I use Force.tick for an animated application of force.
I have a use case where a Entity, after a Force.tick call, comes back with "NaN" values for the x and y fields.
Here is the data. The logs happened immediatly before I call Force.tick and immediatly after I call it. I pretty-printed them for easier comprehension:
Input Force.tick:
Entities:
(x, y ) |(vx, vy)
-------------------------------------------|--------
((526.5759052118638, 293.3027712572425), |(0, 0))
((386.3512835644626, 415.9671753206878), |(0, 0))
((429.3073689756422, 189.14562685359772),|(0, 0))
((518.5317355646032, 355.4308242590064), |(0, 0))
((139.23370668342812, 246.1536023094656), |(0, 0))
----------------------------------------------------
Simulation-State:
{ alpha = 1
, alphaDecay = 0.02276277904418933
, alphaTarget = 0
, forces =
[ Links 1
[{ bias = 0.5, distance = 150, source = "3", strength = 0.5, target = "4" }
,{ bias = 0.5, distance = 150, source = "2", strength = 0.5, target = "4" }
,{ bias = 0.5, distance = 150, source = "1", strength = 0.5, target = "3" }
,{ bias = 0.5, distance = 150, source = "1", strength = 0.5, target = "2" }
,{ bias = 0.5, distance = 150, source = "0", strength = 0.5, target = "0" } -- source and target are the same here, but that is a valid state in my case!
]
, ManyBody 0.9
( Dict.fromList
[("0",-500)
,("1",-500)
,("2",-500)
,("3",-500)
,("4",-500)
]
)
,Center 400 300
]
, minAlpha = 0.001
, velocityDecay = 0.6
}
=====================================================
Output Force.tick:
(x, y ) |(vx, vy)
-------------------------------------------|--------
((527.2044879394728, 293.5724313492498), |(0, 0))
((386.14888774606607, 416.9074137596222), |(0, 0))
((430.6045558668222, 189.20225383801645),|(0, 0))
((520.0468122000351, 354.82321315627416),|(0, 0))
((NaN, NaN), |(0, 0))
----------------------------------------------------
Notice that the edge in question is a self-reference, so thi smight be the reason for this behavior? I can work around for it now, but maybe this points to a bigger issue?
Hi, when I try to render line with less than 4 values, it doesn't render line. Is that intended or is it a bug?
He is minimalistic ellie reproduction https://ellie-app.com/3MLF4BVzDPKa1
Thanks π
I'm working on a responsive design, and I'd really like to have the graph's width adjust based on the view port. I'm currently not aware of a way to get a DOM element's width in Elm, and the scales appear to require a hard width to render correctly.
Do you have any ideas or workarounds for this?
I'm making some issues for what used to be TODO's.
There's a common D3 axis trick to set the ticks hight to a negative value to obtain a grid.
We can use that trick here as well, but the lines are drawn with stroke "#000"
resulting in strong black lines.
A quick improvement would be to change the stroke value to "currentColor"
.
I hope I have not simply misunderstood the docs :) From my understanding though, the Options
passed to Axis.axis
has a tickCount
that determines roughly how many ticks to generate. This does not seem to work for me. I created a gist based on the line chart example, where I set tickCount = 5
on both axis, yet the x-axis has way too many ticks:
https://gist.github.com/SimonPersson/85e571756b20d94b6d8f9eca8a37fa1e
No issue, just wanted to say thank you for bringing D3 to Elm!
Amazing, super, wow.
Better than D3. (For what I'm doing!)
This + TypedSVG is super powerful.
Rock on,
Adam
I'm making some issues for what used to be TODO's
Also Check for aspect ratio, currentScale, currentTranslate, etc...
Hello Jakub,
@folkertdev just released a new version of one-true-path-experiment, with fixes for path and floating number coordinates parsing. Unfortunately the path parser fix imply a major version bump for it, so elm-visualization can't match it. Would you mind bumping this version dep? (I can make a PR to prepare this).
Have a nice day,
Emilien
The plan is to aggregate major breaking changes that would be worthwhile to make, so that a major release can be coordinated.
Change Visualization.List.range
from number -> number -> number -> List number
to Float -> Float -> Float -> List Float
. This would enable much simpler and more correct code (partially this is due to compiler bugs, but not entirely).
Fix the bad type signatures for quantize scales to enable them to be compatible with Axis. See #5.
Reconsider argument order for Scales. See #7.
Consider removing Path module in favor of a third party library, see https://github.com/folkertdev/one-true-path-experiment. This would potentially make animation significantly easier, if animation libraries also migrate to this shared common core.
Open up the Curve
data type by changing it to a type alias for List (Float, Float)
Remove module prefixes. So Visualization.Shape
would become Shape
. Not entirely sure what to do with Visualization.List
yet. One option is to make this module private as it mostly contains utility functions used elsewhere. Another is to simply rename it to something like ListUtilities
.
I'm implementing a zoomable time axis (from seconds up to months, ) and ContinuousTimeScale
works almost perfectly out of the box but for certain zoom levels the resulting ticks could be improved.
As the ideal result seems quite case specific I was wondering if it would make sense to add the ability to pass custom tickIntervals through to the Scale? Essentially what I would want is to modify this https://github.com/gampleman/elm-visualization/blob/master/src/Visualization/Scale/Time.elm#L51 but due to most of the machinery being private modifying it without forking the whole library seems a no go.
I'm sure this is already on the radar, but I thought I'd make a ticket so we can all focus our attention. I will look into helping with this and possibly adding a PR if no-one beats me to it.
Not sure if I'm not doing something right, but I have a 1d (X) brush that works perfectly only when the outer width exactly matches the viewBox width. When it is scaled up (the actual width > viewBox width), the left side of the brush overlay gets shifted to the right, and by a factor of the x position in the SVG so that towards the left side it's almost correct, but as you move right, it gets further and further offset. Likewise when it is scaled down (actual width < viewBox width), the left side of the overlay gets shifted to the left. Otherwise, other manipulation of the brush (moving, stretching, dragging etc.) works fine, it's just the initial positioning of the brush that's off.
Unless I'm doing something wrong, I suspect something in the event decoders is not working right. Perhaps decodeSVGTransformMatrix is not finding and therefore adjusting by the viewBox dimensions? Maybe be related to #92 ? My understanding of this corner of the DOM is pretty sketchy, so I'm not clear how you are getting to the viewBox which is in an ancestor node, from the top-level brush g which I'd think is the currentTarget
? Does baseVal
somehow magically get you the viewBox of its ancestor nodes?
Thanks for any suggestions. elm-visualization rocks.
decodeSVGTransformMatrix : Decoder (Maybe Matrix2x3)
decodeSVGTransformMatrix =
D.oneOf
[ D.map3
(\viewBox width height ->
Just ( ( viewBox.width / width, 0, 0 ), ( 0, viewBox.height / height, 0 ) )
)
(D.at [ "currentTarget", "viewBox", "baseVal" ] decodeRect)
(D.at [ "currentTarget", "width", "baseVal", "value" ] D.float)
(D.at [ "currentTarget", "height", "baseVal", "value" ] D.float)
, D.succeed Nothing
]
I would like to implement a custom scale, but since Scale doesn't expose it's type constructor, I can't see a way to do it. Or am I missing something?
What I would like is for
elm-visualization/src/Scale.elm
Lines 1 to 2 in a336fc1
Scale(..)
.
Thanks!
Depends on this issue in elm-analyse.
I just read: https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html
Seems like we should ship with some carefully curated color maps.
Hi,
This looks nice, is this project still alive?
The link to the examples (http://code.gampleman.eu/elm-visualization/) seems to be broken, DNS unreachable.
Paulos
I would like to figure out a way to track performance so that subsequent commits don't backslide the performance gains (at least inadvertently) from micro optimizations like #36. For example reverting the changes made in that PR would be totally reasonable in a code cleanup PR that a reviewer (who doesn't remember why they were made) wouldn't blink twice about.
Originally posted by @gampleman in #36
When the logarithmic Scale.nice
is applied to Scale.log
with a positive domain close to 0 it causes the final resulting domain to be reversed eg, (9, 4)
The resulting plot axis ends up being reversed.
I'll provide more details with examples
It would be nice if there was a CI to double check that the tests pass and the code compiles. Bonus if we can get image diffing on the examples.
This package uses elm-community/list-extra v6.Meaning that it cannot be used with project using elm-community/list-extra v7 (our case). Please upgrade.
For a long time I've made modules with (hopefully) a relatively coherent API that tackle some sort of problem. Usually I've omitted some of the slightly less used parts to save some time and get stuff out of the door.
But I believe it's time to make a release that fills out those blanks to make a more comprehensive library.
So here is the checklist what I would like to see done:
collision
force πx
forcey
forceradial
force πProbably drop:
(:mag: means that this feature will also need an example demonstrating how to use it).
This is a fairly ambitions plan and I would appreciate some help. Typically a lot of the design of these apis should simply follow the patterns already established in the module (remember, this will be a MINOR release, so no breaking changes allowed). As such this will be mostly about implementation, docs and examples. (Of course if while working on these you think you discovered a better API design, please open an issue or get in touch on Slack. Just because we won't ship it in this release doesn't mean we won't improve it at all. But I do like to batch breaking changes so the library mostly provides a stable target with only very rare upgrade costs).
So if you would like to contribute one of these, please let me know. Thanks!
Note: None of this is going to happen any time soon. The last breaking change we made was forced by a new version of Elm. But it is a good idea to keep a wish list of things to change in the future.
invertExtent
to support threshold scales.Shape.stack
can be made more intuitive.See also #10.
I get an error calling Axis.axis with a QuantizeScale. It looks like the types don't line up for ticks
and tickFormat
. Am I missing something?
{ domain : (Float, Float)
, range : (a, List a)
, convert : (Float, Float) -> (a, List a) -> Float -> a
, invertExtent : (Float, Float) -> (a, List a) -> a -> Maybe (Float, Float)
, ticks : (Float, Float) -> (a, List a) -> Int -> List Float
, tickFormat : (Float, Float) -> (a, List a) -> Int -> Float -> String
, nice : (Float, Float) -> Int -> (Float, Float)
, rangeExtent : (Float, Float) -> (a, List a) -> (a, a)
}
{ a |
ticks : domain -> Int -> List value
, domain : domain
, tickFormat : domain -> Int -> value -> String
, convert : domain -> range -> value -> Float
, range : range
, rangeExtent : domain -> range -> (Float, Float)
}
Hello
I am trying to make a force layout simulation that displays the graph in a tree like way. By "tree-like", I mean I want the resulting layout to look something like a hierarchical layout where parent nodes will be displayed above child nodes, left child nodes are placed to the left of their sibling nodes, etc. These aren't hard constraints, but I want the layout to trend towards local placements close to this:
parent
/ \
leftChild rightChild
I don't want to use Sugiyama because it is complicated to turn that into an incremental algorithm (nodes in my visualization will be continually added and removed).
My main idea right now is to coarsen the graph by treating some of the local 3-node subgraphs (parent, leftChild, rightChild) as single nodes, and applying a force simulation on that simplified graph. Maybe even treating 2-node subgraphs of (parent, leftChild) as single nodes would be enough.
Is there a more elegant/simple approach?
Thank you
Hi, thank you for all the great work! I use this package for zooming and panning in a user interface, and it works wonderfully.
The only issue I have is that the onWheel
event handles both the scrolling and the pinch-to-zoom gestures, whereas it would be preferable if scrolling were still possible, as it is for example in the zoom-and-pan interfaces of Sketch and Metro.io.
According to this article, the solution could be to add (D.field "ctrlKey" D.bool)
to the decoder in the onWheel
function:
When you perform a pinch-zoom gesture, Firefox and Chrome produce a wheel event with a
deltaY: Β±scale
,ctrlKey: true
.
If ctrlKey
is False
, the message could be a NoOp
and the preventDefault
in the event listener could be removed to allow scrolling.
One of things that always bugs me are the types in the Scale module. They:
The reason they were designed that way is that I wanted to have a generic Scale type, that could be operated on regardless of what kind of scale there was, yet not all scales support all operations. I knew I definitely didn't want runtime failures (i.e. unnecessary Maybes), hence this design.
One option is trying to figure out some phantom types to simplify this, but I have tried this before and not made much progress.
The other option is to abandon the generic Scale type and simply have a module and type for each kind of scale (i.e. there would be Scale.Continuous
, Scale.Sequential
, etc). This would make the types straightforward and the internal logic equally so. Each of these would need an adaptor function for Axes (which we already kind of have for band scales). All the above disadvantages would go away, however there are some downsides:
Anyway, this issue is just putting down some thoughts. I have not committed to this, I am seeking feedback on these options, so please feel free to offer your opinion.
to replicate the error, replace in the example the lines
graph =
Graph.mapContexts initializeNode miserablesGraph
by
graph =
Graph.mapContexts initializeNode miserablesGraph
|> Graph.update 1 (Maybe.map (updateNode ( 123, 123 )))
|> Graph.update 2 (Maybe.map (updateNode ( 123, 123 )))
We could make add
and delete
options to be a -> Interpolator (Maybe a)
, with Nothing
indicating their absence in the resulting list.
We should also make the list ordered by default where order would be interpolated properly.
ListCombiner
is kind of dumb, we should just accept a List (Interpolator a) -> Interpolator (List a)
function
Finally, we could make all the options (except id
) actually optional, since
{ add = \v -> Interpolation.step Nothing [ Just v ]
, remove = \v -> Interpolation.step (Just v) Nothing
, change = \a b -> Interpolation.step a [ b ]
, combine = Interpolation.inParallel
}
would be pretty decent defaults.
Potentially we could make id
optional as well and just treat everything as additions and removals without it, but that sort of seems fairly suboptimal. Or have a way to use ==
to figure out reorder-ing at least...
My intuition is that elm-visualization may be a library that is slightly tricky to use for a beginner for the following reasons:
renderBarChart : List (Float, Float) -> Svg msg
) and so you are left to figure out how to glue the functions together on your own.I attempted to solve (2) by providing a gallery of various examples, so that learners can see some sample code on how to achieve various things.
However, I would like to know:
So, if you are a user of elm-visualization, please add your experiences. I'll try to come up with a plan of attack later based on feedback.
Axis
, which are a bit like that.Hi and thanks for providing elm-visualization!
It would be great to be able to rotate the labels on the axes.
As a concrete use case, I'm trying to build a bar chart based on the bar chart example (https://code.gampleman.eu/elm-visualization/BarChart/).
Currently, the labels overlap when they're too long, which makes them unreadable.
I've tried to rotate the columns individually in SVG by using the rotate(90)
transformation in the browser. However, there are two problems with this approach.
I've also tried to apply the rotation transformation to the .tick class in the example. However, then the translation of individual columns doesn't work anymore. Furthermore, this approach applies the transformation to the x and y axis. Finally, the problem with alignment would remain.
As a third approach I've limited the size of the labels. In this case some labels turn out to be duplicates, which leads to errors in the rendering:
I'd really appreciate any help on this issue.
Cheers,
Peter
Thank you very much for writing this library, it appears to be both practically useful as well as a good education in how to think about charts.
Can I suggest that you have a look at the documentation for Scale
? It isn't very clear which of the parameters to the ContinuousScale
function is the domain and which is the range. There is a clue in the Scale.time
function, where the Posix
values must be the domain, but for instance, Scale.linear
has the signature Scale.linear: (Float, Float) -> (Float, Float) -> ContinuousScale Float
, which is more opaque.
Perhaps an example like:
makeContinuousX : (Float, Float) -> (Float, Float) -> ContinuousScale Float
makeContinuousX domain range =
Scale.linear domain range
-- Create a scale that maps inputs between 0 and 1 to a range of 100 pixels
makeContinuousX (0,100) (0,1)
Or whatever. I'm sure you'll have a better idea than me!
So I'm trying to get my bar chart to display a mouseover, which in it self is not a problem.
Modification of your official stacked bar chart example, notice the String parameter embedded in the second param and the title (from typed svg) call:
column : BandScale String -> ( String, List ( Float, Float, String ) ) -> Svg msg
column xScale ( xAxisLabel, values ) =
let
block color ( upperY, lowerY, str ) =
rect
[ x <| Scale.convert xScale xAxisLabel
, y <| lowerY
, width <| Scale.bandwidth xScale
, height <| (abs <| upperY - lowerY)
, fill (Fill color)
]
[ title [] [ text str ] ]
in
g [ class [ "column" ] ] (List.map2 block colors values)
But how do I get the data I want to display in the tooltips?
column
is called like this in your example
List.map (column xScale) (List.map2 (\a b-> ( a, b )) xAxisLabels scaledValues)
So I changed
scaledValues =
List.map (List.map (\( y1, y2 ) -> ( Scale.convert yScale y1, Scale.convert yScale y2, "this will get displayed" ))) transposedValues
but how do i get the transposedValues or values to still have the original values?
I suspect Stackresult needs to be changed to not only generate yLower
and yUpper
but also an original value to handle this gracefully?
Clicking on a circle results the circle to be displaced with some offset from the mouse.
This offset gets larger as the drag coordinates get larger.
So at (x:0, y:0) the circle is located at the position of the mouse, for larger coordinates this is no longer the case. This offset getting larger implies the drag coordinates are erroneously multiplied by some factor.
I looked around somewhat but could not find the cause of this bug.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.