From 99988382379d55d23fad3c3e852dc67a0418ad53 Mon Sep 17 00:00:00 2001 From: Tom Schindler <tom.schindler@student.uni-halle.de> Date: Wed, 26 Jun 2024 20:39:19 +0200 Subject: [PATCH] Vorbereitung Parallele Koordinaten --- public/main.js | 2 +- src/Main.elm | 2 + src/ParallelCoordinates.elm | 449 ++++++++++++++++++++++++++++++++++++ 3 files changed, 452 insertions(+), 1 deletion(-) create mode 100644 src/ParallelCoordinates.elm diff --git a/public/main.js b/public/main.js index a59ab94..c4597f8 100644 --- a/public/main.js +++ b/public/main.js @@ -8848,7 +8848,7 @@ var $author$project$Scatterplot$scatterplot = function (model) { _List_Nil, _List_fromArray( [ - $elm_community$typed_svg$TypedSvg$Core$text('\n .point circle { stroke: rgba(0, 0, 0,0.4); fill: rgba(255, 255, 255,0.3); }\n .point text { display: none; }\n .point:hover circle { stroke: rgba(0, 0, 0,1.0); fill: rgb(118, 214, 78); }\n .point:hover text { display: inline; }\n ') + $elm_community$typed_svg$TypedSvg$Core$text('\r\n .point circle { stroke: rgba(0, 0, 0,0.4); fill: rgba(255, 255, 255,0.3); }\r\n .point text { display: none; }\r\n .point:hover circle { stroke: rgba(0, 0, 0,1.0); fill: rgb(118, 214, 78); }\r\n .point:hover text { display: inline; }\r\n ') ])), A2( $elm_community$typed_svg$TypedSvg$g, diff --git a/src/Main.elm b/src/Main.elm index ddb4c6c..1fdf593 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -1,5 +1,7 @@ module Main exposing (..) +--import ParallelCoordinates exposing (ParallelPlotOption) + import Browser import Csv.Decode as Decode exposing (Decoder) import Html exposing (Html, div) diff --git a/src/ParallelCoordinates.elm b/src/ParallelCoordinates.elm new file mode 100644 index 0000000..deb57eb --- /dev/null +++ b/src/ParallelCoordinates.elm @@ -0,0 +1,449 @@ +module ParallelCoordinates exposing (..) + +import Axis exposing (..) +import Browser +import Color +import Debug exposing (toString) +import Html exposing (Html, a, b, br, button, text) +import Html.Attributes +import Html.Events exposing (on, onClick) +import List exposing (concatMap) +import List.Extra +import Maybe exposing (map) +import Path +import Scale exposing (ContinuousScale) +import Shape +import SmartPhoneType exposing (Smartphone) +import Statistics +import TypedSvg exposing (circle, g, line, polygon, rect, style, svg, text_) +import TypedSvg.Attributes exposing (class, fill, fontSize, stroke, strokeWidth, textAnchor, transform, viewBox, width, x1, x2, y1, y2) +import TypedSvg.Attributes.InPx exposing (cx, cy, r, x, y) +import TypedSvg.Core exposing (Svg) +import TypedSvg.Types exposing (AnchorAlignment(..), Length(..), Paint(..), Transform(..), px) + + + +{- + type alias ParallelPlotOption = + { attribute1 : String + , attribute2 : String + , att1List : List (Maybe Float) + , att2List : List (Maybe Float) + } + + + w : Float + w = + 900 + + + h : Float + h = + 450 + + + padding : Float + padding = + 50 + + + tickCount : Int + tickCount = + 3 + + + defaultExtent : ( number, number1 ) + defaultExtent = + ( 0, 200000 ) + + + parallelCoodinatesPlot : MultiDimData -> Svg msg + parallelCoodinatesPlot multidimData = + let + dimLen = + List.length multidimData.data - 1 + + yValues = + multidimData.data + |> List.map + (\dimData -> + dimData |> List.map .value + ) + + dimStrings = + multidimData.dimDescription + + yScaleLocals = + List.indexedMap Tuple.pair (List.map yScale yValues) + + xScaleLocal = + xScale (List.map toFloat (List.range 0 dimLen)) + + yaxis = + List.map2 (createYAxis xScaleLocal) yScaleLocals dimStrings + + yValue1 = + Maybe.withDefault [] <| List.head yValues + + yValue2 = + Maybe.withDefault [] <| List.head <| List.drop 1 yValues + + yValue3 = + Maybe.withDefault [] <| List.head <| List.drop 2 yValues + + yValue4 = + Maybe.withDefault [] <| List.head <| List.drop 3 yValues + + yScale1 = + Tuple.second <| Maybe.withDefault ( 0, Scale.linear ( padding, w - padding ) ( 0, 0 ) ) (List.head yScaleLocals) + + yScale2 = + Tuple.second <| Maybe.withDefault ( 0, Scale.linear ( padding, w - padding ) ( 0, 0 ) ) (List.head <| List.drop 1 yScaleLocals) + + yScale3 = + Tuple.second <| Maybe.withDefault ( 0, Scale.linear ( padding, w - padding ) ( 0, 0 ) ) (List.head <| List.drop 2 yScaleLocals) + + yScale4 = + Tuple.second <| Maybe.withDefault ( 0, Scale.linear ( padding, w - padding ) ( 0, 0 ) ) (List.head <| List.drop 3 yScaleLocals) + + paths = + List.map4 pathGenerator yValue1 yValue2 yValue3 yValue4 + + pathGenerator : Float -> Float -> Float -> Float -> Path.Path + pathGenerator y1 y2 y3 y4 = + let + p1 = + ( Scale.convert xScaleLocal 0, Scale.convert yScale1 y1 + 20 ) + + p2 = + ( Scale.convert xScaleLocal 1, Scale.convert yScale2 y2 + 20 ) + + p3 = + ( Scale.convert xScaleLocal 2, Scale.convert yScale3 y3 + 20 ) + + p4 = + ( Scale.convert xScaleLocal 3, Scale.convert yScale4 y4 + 20 ) + in + [ Just p1, Just p2, Just p3, Just p4 ] |> Shape.line Shape.linearCurve + + lines = + List.map createLine paths + in + svg + [ viewBox 0 0 (w + 30) (h + 30) + , TypedSvg.Attributes.width <| TypedSvg.Types.Percent 100 + , TypedSvg.Attributes.height <| TypedSvg.Types.Percent 100 + ] + ([ style [] [ TypedSvg.Core.text """ + .axis text { display: none; } + .axis:hover text { display: inline; } + """ ] + , g [] [] + ] + ++ yaxis + ++ lines + ) + + + createLine : Path.Path -> Svg msg + createLine path = + g [] + [ Path.element path + [ stroke <| Paint <| Color.black + , strokeWidth <| Px 1 + , fill PaintNone + ] + ] + + + createYAxis : ContinuousScale Float -> ( Int, ContinuousScale Float ) -> String -> Svg msg + createYAxis scaleX scaleY text = + let + index = + Tuple.first scaleY |> toFloat + in + g [ transform [ Translate (Scale.convert scaleX index) 20 ] ] + [ g [ class [ "axis" ] ] [ yAxis (Tuple.second scaleY) ] + , text_ + [ y -10, textAnchor AnchorMiddle, fontSize <| Px 14 ] + [ Html.text text ] + ] + + + yAxis : ContinuousScale Float -> Svg msg + yAxis scale = + Axis.left [] scale + + + xScale : List Float -> ContinuousScale Float + xScale values = + Scale.linear ( padding, w - padding ) (wideExtent values) + + + yScale : List Float -> ContinuousScale Float + yScale values = + Scale.linear ( h, 0 ) (wideExtent values) + + + wideExtent : List Float -> ( Float, Float ) + wideExtent values = + Maybe.withDefault defaultExtent (Statistics.extent values) + + + filterCarData : + List Car + -> CarType + -> List CarData + filterCarData my_cars carType = + let + carDataList = + List.filterMap mapToCarData my_cars + + carsInType = + List.filter (carInCarType carType) carDataList + in + carsInType + + + mapToCarData : Car -> Maybe CarData + mapToCarData car = + map6 + (\cityMPG retailPrice name dealerCost carLen carType -> + { vehicle = name + , cityMPG = toFloat cityMPG + , retailPrice = toFloat retailPrice + , dealerCost = toFloat dealerCost + , carLen = toFloat carLen + , carType = carType + } + ) + car.cityMPG + car.retailPrice + (Just car.vehicleName) + car.dealerCost + car.carLen + (Just car.carType) + + + carInCarType : CarType -> CarData -> Bool + carInCarType carType carData = + if carData.carType == carType then + True + + else + False + + + transfromCarDataToMultiDimData : List ( String, List ( Float, String ) ) -> MultiDimData + transfromCarDataToMultiDimData dimList = + let + dimDescription = + List.map Tuple.first (List.map ffks dimList) + + data = + List.map Tuple.second (List.map ffks dimList) + in + MultiDimData dimDescription data + + + ffks : ( String, List ( Float, String ) ) -> ( String, List MultiDimPoint ) + ffks data = + let + multiDimPointList = + List.map transformToMultiDimPoint (Tuple.second data) + in + ( Tuple.first data, multiDimPointList ) + + + transformToMultiDimPoint : ( Float, String ) -> MultiDimPoint + transformToMultiDimPoint data = + MultiDimPoint (Tuple.second data) (Tuple.first data) + + + cityMPGValue : CarData -> ( Float, String ) + cityMPGValue car = + ( car.cityMPG, car.vehicle ) + + + retailPriceValue : CarData -> ( Float, String ) + retailPriceValue car = + ( car.retailPrice, car.vehicle ) + + + dealerCostValue : CarData -> ( Float, String ) + dealerCostValue car = + ( car.dealerCost, car.vehicle ) + + + carLenValue : CarData -> ( Float, String ) + carLenValue car = + ( car.carLen, car.vehicle ) + + + pointNameValue : CarData -> String + pointNameValue = + .vehicle + + + getIndex : String -> Model -> Int + getIndex str model = + let + rangeList = + List.range 0 <| List.length model.reihenfolge - 1 + + findIndex : Int -> Maybe Int + findIndex index = + if List.Extra.getAt index model.reihenfolge == Just str then + Just index + + else + Nothing + + i = + List.filterMap findIndex rangeList + in + Maybe.withDefault 0 (List.head i) + + + view : Model -> Html Msg + view model = + let + carType = + model.carType + + filteredCarsData = + filterCarData cars carType + + numberCarsInType = + List.length filteredCarsData + + cityMPGValues = + List.map cityMPGValue filteredCarsData + + retailPriceValues = + List.map retailPriceValue filteredCarsData + + dealerCostValues = + List.map dealerCostValue filteredCarsData + + carLenValues = + List.map carLenValue filteredCarsData + + iCityMPG = + getIndex "cityMPG" + + dimList = + List.map Tuple.second + (List.sortBy Tuple.first + [ ( getIndex "cityMPG" model, ( "cityMPG", cityMPGValues ) ) + , ( getIndex "retailPrice" model, ( "retailPrice", retailPriceValues ) ) + , ( getIndex "dealerCost" model, ( "dealerCost", dealerCostValues ) ) + , ( getIndex "carLen" model, ( "carLen", carLenValues ) ) + ] + ) + + multiDimData = + transfromCarDataToMultiDimData dimList + in + Html.div [] + [ Html.p [ fontSize <| Px 5 ] + [ Html.text + ("CarType: " + ++ toString carType + ++ " | Cars in Typ: " + ++ String.fromInt numberCarsInType + ) + ] + , b [] [ text "Car Type: " ] + , button [ onClick SetSmall_Sporty_Compact_Large_Sedan ] [ text "SSCLS" ] + , button [ onClick SetSports_Car ] [ text "Sport Car" ] + , button [ onClick SetSUV ] [ text "SUV" ] + , button [ onClick SetWagon ] [ text "Wagon" ] + , button [ onClick SetMinivan ] [ text "Minivan" ] + , button [ onClick SetPickup ] [ text "Pickup" ] + , br [] [] + , button [ onClick (Down "cityMPG") ] [ text "Down cityMPG" ] + , button [ onClick (Up "cityMPG") ] [ text "Up cityMPG" ] + , br [] [] + , button [ onClick (Down "retailPrice") ] [ text "Down retailPrice" ] + , button [ onClick (Up "retailPrice") ] [ text "Up retailPrice" ] + , br [] [] + , button [ onClick (Down "dealerCost") ] [ text "Down dealerCost" ] + , button [ onClick (Up "dealerCost") ] [ text "Up dealerCost" ] + , br [] [] + , button [ onClick (Down "carLen") ] [ text "Down carLen" ] + , button [ onClick (Up "carLen") ] [ text "Up carLen" ] + , br [] [] + , br [] [] + , parallelCoodinatesPlot multiDimData + ] + + + type alias MultiDimPoint = + { pointName : String, value : Float } + + + type alias MultiDimData = + { dimDescription : List String + , data : List (List MultiDimPoint) + } + + + type Msg + = SetSmall_Sporty_Compact_Large_Sedan + | SetSports_Car + | SetSUV + | SetWagon + | SetMinivan + | SetPickup + | Up String + | Down String + + + type alias Model = + { carType : CarType + , reihenfolge : List String + } + + + update : Msg -> Model -> Model + update msg model = + case msg of + SetSmall_Sporty_Compact_Large_Sedan -> + { model | carType = Small_Sporty_Compact_Large_Sedan } + + SetSports_Car -> + { model | carType = Sports_Car } + + SetSUV -> + { model | carType = SUV } + + SetWagon -> + { model | carType = Wagon } + + SetMinivan -> + { model | carType = Minivan } + + SetPickup -> + { model | carType = Pickup } + + Up str -> + { model + | reihenfolge = + if getIndex str model < (List.length model.reihenfolge - 1) then + List.Extra.swapAt (getIndex str model) (getIndex str model + 1) model.reihenfolge + + else + model.reihenfolge + } + + Down str -> + { model + | reihenfolge = + if getIndex str model > 0 then + List.Extra.swapAt (getIndex str model) (getIndex str model - 1) model.reihenfolge + + else + model.reihenfolge + } +-} -- GitLab