Compare commits
10 Commits
124132fdc7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4394d26184 | ||
|
|
9859938df7 | ||
|
|
62fefb0cb1 | ||
|
|
6a4a0f5719 | ||
|
|
5e795ac9f3 | ||
|
|
da1e6f32a5 | ||
|
|
6541d65f3a | ||
|
|
aa7b0813c4 | ||
|
|
5b4cf834a0 | ||
|
|
2a4b5702af |
93
index.html
93
index.html
@@ -1,10 +1,97 @@
|
|||||||
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Ternary Graph Generator</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="style.css"/>
|
||||||
|
<link rel="stylesheet" href="ternary-graph.css"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
ticks:
|
<div id="breadcrumb">
|
||||||
<input type="number" id="ticks"/>
|
<a id="site-title" href="https://nathanmcrae.name">
|
||||||
<div id="svg-container" class="my-class"></div>
|
<img class="avatar" src="avatar.png"/>
|
||||||
|
NathanMcRae.name
|
||||||
|
</a>
|
||||||
|
/
|
||||||
|
Ternary Graph Generator
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<div id="main-content">
|
||||||
|
<div id="controls">
|
||||||
|
<div class="input">
|
||||||
|
<p>Axis title text size (px):</p>
|
||||||
|
<input type="number" id="axis-title-size" value="16"/>
|
||||||
|
<button id="axis-title-size-down">▼</button>
|
||||||
|
<button id="axis-title-size-up">▲</button>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<p>Tick label text size (px):</p>
|
||||||
|
<input type="number" id="tick-label-size" value="12"/>
|
||||||
|
<button id="tick-label-size-down">▼</button>
|
||||||
|
<button id="tick-label-size-up">▲</button>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<p>Ticks:</p>
|
||||||
|
<input type="number" id="ticks" value="9"/>
|
||||||
|
<button id="ticks-down">▼</button>
|
||||||
|
<button id="ticks-up">▲</button>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<p>Tick size (px):</p>
|
||||||
|
<input type="number" id="tick-size" value="5"/>
|
||||||
|
<button id="tick-size-down">▼</button>
|
||||||
|
<button id="tick-size-up">▲</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group">
|
||||||
|
<h3>Axis 1</h3>
|
||||||
|
<div class="input">
|
||||||
|
<p>Title:</p>
|
||||||
|
<input id="axis-1-title" value="Axis 1"/>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<p>Start value:</p>
|
||||||
|
<p>E</p>
|
||||||
|
<input type="number" id="axis-1-start" value="0"/>
|
||||||
|
<button id="axis-1-start-down">▼</button>
|
||||||
|
<button id="axis-1-start-up">▲</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
<h3>Axis 2</h3>
|
||||||
|
<div class="input">
|
||||||
|
<p>Title:</p>
|
||||||
|
<input id="axis-2-title" value="Axis 2"/>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<p>Start value:</p>
|
||||||
|
<p>E</p>
|
||||||
|
<input type="number" id="axis-2-start" value="0"/>
|
||||||
|
<button id="axis-2-start-down">▼</button>
|
||||||
|
<button id="axis-2-start-up">▲</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
<h3>Axis 3</h3>
|
||||||
|
<div class="input">
|
||||||
|
<p>Title:</p>
|
||||||
|
<input id="axis-3-title" value="Axis 3"/>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<p>Start value:</p>
|
||||||
|
<p>E</p>
|
||||||
|
<input type="number" id="axis-3-start" value="0"/>
|
||||||
|
<button id="axis-3-start-down">▼</button>
|
||||||
|
<button id="axis-3-start-up">▲</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a id="download-button">
|
||||||
|
<button>Download SVG</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="svg-container" class="my-class"></div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
</html>
|
</html>
|
||||||
@@ -19,6 +19,7 @@ to generate this file without the comments in this block.
|
|||||||
, "exceptions"
|
, "exceptions"
|
||||||
, "foldable-traversable"
|
, "foldable-traversable"
|
||||||
, "integers"
|
, "integers"
|
||||||
|
, "js-uri"
|
||||||
, "lists"
|
, "lists"
|
||||||
, "maybe"
|
, "maybe"
|
||||||
, "numbers"
|
, "numbers"
|
||||||
|
|||||||
202
src/Main.purs
202
src/Main.purs
@@ -15,7 +15,9 @@ import Effect (Effect)
|
|||||||
import Effect.Console (log)
|
import Effect.Console (log)
|
||||||
import Effect.Class (liftEffect)
|
import Effect.Class (liftEffect)
|
||||||
import Effect.Exception (throw)
|
import Effect.Exception (throw)
|
||||||
|
import JSURI (encodeURIComponent)
|
||||||
import TernaryGraph (Dimension, svgTextID, ternaryGraph, ternaryGraphSvg, TextStyle, tickLabelStrings)
|
import TernaryGraph (Dimension, svgTextID, ternaryGraph, ternaryGraphSvg, TextStyle, tickLabelStrings)
|
||||||
|
import TernaryGraph as TernaryGraph
|
||||||
import Web.DOM.Document (contentType
|
import Web.DOM.Document (contentType
|
||||||
, createElement
|
, createElement
|
||||||
, Document
|
, Document
|
||||||
@@ -104,35 +106,92 @@ getAllTextDimensions document svgContainer strings = do
|
|||||||
|
|
||||||
pure myMap
|
pure myMap
|
||||||
|
|
||||||
update :: Event -> Effect Unit
|
updateEvent :: Event -> Effect Unit
|
||||||
update e = do
|
updateEvent _ = do update
|
||||||
|
|
||||||
|
update :: Effect Unit
|
||||||
|
update = do
|
||||||
w <- window
|
w <- window
|
||||||
d <- document w
|
d <- document w
|
||||||
let document = HTMLDoc.toDocument d
|
let document = HTMLDoc.toDocument d
|
||||||
domParser <- makeDOMParser
|
domParser <- makeDOMParser
|
||||||
|
|
||||||
log "update"
|
axis1TitleElMay <- myGetElementById document "axis-1-title"
|
||||||
|
axis1TitleEl <- case HTMLInput.fromElement axis1TitleElMay of
|
||||||
|
Nothing -> throw "'axis-1-title' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
axis1Title <- HTMLInput.value axis1TitleEl
|
||||||
|
|
||||||
|
axis1StartElMay <- myGetElementById document "axis-1-start"
|
||||||
|
axis1StartEl <- case HTMLInput.fromElement axis1StartElMay of
|
||||||
|
Nothing -> throw "'axis-1-start' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
axis1Start <- (liftM1 Int.round) $ HTMLInput.valueAsNumber axis1StartEl
|
||||||
|
|
||||||
|
axis2TitleElMay <- myGetElementById document "axis-2-title"
|
||||||
|
axis2TitleEl <- case HTMLInput.fromElement axis2TitleElMay of
|
||||||
|
Nothing -> throw "'axis-2-title' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
axis2Title <- HTMLInput.value axis2TitleEl
|
||||||
|
|
||||||
|
axis2StartElMay <- myGetElementById document "axis-2-start"
|
||||||
|
axis2StartEl <- case HTMLInput.fromElement axis2StartElMay of
|
||||||
|
Nothing -> throw "'axis-2-start' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
axis2Start <- (liftM1 Int.round) $ HTMLInput.valueAsNumber axis2StartEl
|
||||||
|
|
||||||
|
axis3TitleElMay <- myGetElementById document "axis-3-title"
|
||||||
|
axis3TitleEl <- case HTMLInput.fromElement axis3TitleElMay of
|
||||||
|
Nothing -> throw "'axis-3-title' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
axis3Title <- HTMLInput.value axis3TitleEl
|
||||||
|
|
||||||
|
axis3StartElMay <- myGetElementById document "axis-3-start"
|
||||||
|
axis3StartEl <- case HTMLInput.fromElement axis3StartElMay of
|
||||||
|
Nothing -> throw "'axis-3-start' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
axis3Start <- (liftM1 Int.round) $ HTMLInput.valueAsNumber axis3StartEl
|
||||||
|
|
||||||
inputElement <- myGetElementById document "ticks"
|
inputElement <- myGetElementById document "ticks"
|
||||||
inputHTMLElement <- case HTMLInput.fromElement inputElement of
|
inputHTMLElement <- case HTMLInput.fromElement inputElement of
|
||||||
Nothing -> throw "'ticks' element is not an input tag"
|
Nothing -> throw "'ticks' element is not an input tag"
|
||||||
Just e -> pure e
|
Just e -> pure e
|
||||||
ticks <- (liftM1 Int.round) $ HTMLInput.valueAsNumber inputHTMLElement
|
ticks <- (liftM1 Int.round) $ HTMLInput.valueAsNumber inputHTMLElement
|
||||||
|
|
||||||
|
tickSizeElMay <- myGetElementById document "tick-size"
|
||||||
|
tickSizeEl <- case HTMLInput.fromElement tickSizeElMay of
|
||||||
|
Nothing -> throw "'tick-size' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
tickSize <- HTMLInput.valueAsNumber tickSizeEl
|
||||||
|
|
||||||
|
axisTitleSizeElMay <- myGetElementById document "axis-title-size"
|
||||||
|
axisTitleSizeEl <- case HTMLInput.fromElement axisTitleSizeElMay of
|
||||||
|
Nothing -> throw "'axis-title-size' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
axisTitleSize <- HTMLInput.valueAsNumber axisTitleSizeEl
|
||||||
|
|
||||||
|
tickLabelSizeElMay <- myGetElementById document "tick-label-size"
|
||||||
|
tickLabelSizeEl <- case HTMLInput.fromElement tickLabelSizeElMay of
|
||||||
|
Nothing -> throw "'tick-label-size' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
tickLabelSize <- HTMLInput.valueAsNumber tickLabelSizeEl
|
||||||
|
|
||||||
svgContainer <- getNodeById document "svg-container"
|
svgContainer <- getNodeById document "svg-container"
|
||||||
|
|
||||||
let graphDef = { axis1Label: "axis 1"
|
let graphDef = { axis1Label: axis1Title
|
||||||
, axis2Label: "axis 2"
|
, axis2Label: axis2Title
|
||||||
, axis3Label: "axis 3"
|
, axis3Label: axis3Title
|
||||||
, axis1Start: 0
|
, axis1Start: axis1Start
|
||||||
, axis2Start: 1
|
, axis2Start: axis2Start
|
||||||
, axis3Start: 20
|
, axis3Start: axis3Start
|
||||||
, numTicks: ticks
|
, numTicks: ticks
|
||||||
, tickTextStyle: { sizePx: 12.0
|
, tickTextStyle: { sizePx: tickLabelSize
|
||||||
, typeface: "Liberation Sans"
|
|
||||||
}
|
|
||||||
, axisTitleTextStyle: { sizePx: 16.0
|
|
||||||
, typeface: "Liberation Mono"
|
, typeface: "Liberation Mono"
|
||||||
}
|
}
|
||||||
|
, tickSize: TernaryGraph.Pixels tickSize
|
||||||
|
, axisTitleTextStyle: { sizePx: axisTitleSize
|
||||||
|
, typeface: "Liberation Sans"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tickText = tickLabelStrings graphDef
|
let tickText = tickLabelStrings graphDef
|
||||||
@@ -170,67 +229,76 @@ update e = do
|
|||||||
newNode <- importNode svgNode true document
|
newNode <- importNode svgNode true document
|
||||||
appendChild newNode svgContainer
|
appendChild newNode svgContainer
|
||||||
|
|
||||||
|
-- Set SVG content to download button
|
||||||
|
downloadButtonEl <- myGetElementById document "download-button"
|
||||||
|
encodedSVG <- case encodeURIComponent mySVG of
|
||||||
|
Nothing -> throw "Failed to encode SVG"
|
||||||
|
Just encoded -> pure encoded
|
||||||
|
let downloadText = "data:xml/svg;charset=utf-8," <> encodedSVG
|
||||||
|
Element.setAttribute "href" downloadText downloadButtonEl
|
||||||
|
Element.setAttribute "download" "ternary-graph.svg" downloadButtonEl
|
||||||
|
|
||||||
|
addUpdateListener :: Document -> String -> Effect Unit
|
||||||
|
addUpdateListener doc id = do
|
||||||
|
listener <- eventListener updateEvent
|
||||||
|
element <- myGetElementById doc id
|
||||||
|
addEventListener (EventType "input") listener true (Element.toEventTarget element)
|
||||||
|
|
||||||
|
incrementInput :: HTMLInput.HTMLInputElement -> Number -> Event -> Effect Unit
|
||||||
|
incrementInput input inc _ = do
|
||||||
|
currentVal <- HTMLInput.valueAsNumber input
|
||||||
|
HTMLInput.setValueAsNumber (currentVal + inc) input
|
||||||
|
update
|
||||||
|
|
||||||
|
registerInputIncButton :: Document -> String -> String -> Number -> Effect Unit
|
||||||
|
registerInputIncButton doc inputID buttonID inc = do
|
||||||
|
inputElMay <- myGetElementById doc inputID
|
||||||
|
inputEl <- case HTMLInput.fromElement inputElMay of
|
||||||
|
Nothing -> throw $ "'" <> inputID <> "' element is not an input tag"
|
||||||
|
Just e -> pure e
|
||||||
|
|
||||||
|
listener <- eventListener $ incrementInput inputEl inc
|
||||||
|
element <- myGetElementById doc buttonID
|
||||||
|
addEventListener (EventType "click") listener true (Element.toEventTarget element)
|
||||||
|
|
||||||
main :: Effect Unit
|
main :: Effect Unit
|
||||||
main = do
|
main = do
|
||||||
w <- window
|
w <- window
|
||||||
d <- document w
|
d <- document w
|
||||||
let dd = HTMLDoc.toDocument d
|
let dd = HTMLDoc.toDocument d
|
||||||
domParser <- makeDOMParser
|
|
||||||
|
|
||||||
svgContainer <- getNodeById dd "svg-container"
|
addUpdateListener dd "axis-1-title"
|
||||||
|
addUpdateListener dd "axis-1-start"
|
||||||
let graphDef = { axis1Label: "axis 1"
|
addUpdateListener dd "axis-2-title"
|
||||||
, axis2Label: "axis 2"
|
addUpdateListener dd "axis-2-start"
|
||||||
, axis3Label: "axis 3"
|
addUpdateListener dd "axis-3-title"
|
||||||
, axis1Start: 0
|
addUpdateListener dd "axis-3-start"
|
||||||
, axis2Start: 1
|
|
||||||
, axis3Start: 20
|
|
||||||
, numTicks: 10
|
|
||||||
, tickTextStyle: { sizePx: 12.0
|
|
||||||
, typeface: "Liberation Sans"
|
|
||||||
}
|
|
||||||
, axisTitleTextStyle: { sizePx: 16.0
|
|
||||||
, typeface: "Liberation Mono"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tickText = tickLabelStrings graphDef
|
|
||||||
|
|
||||||
log $ Array.intercalate ",\n" (Set.toUnfoldable tickText)
|
|
||||||
|
|
||||||
let tickTextStyles = Foldable.foldr (\text textStyleArray -> Array.cons (Tup.Tuple text graphDef.tickTextStyle) textStyleArray) [] tickText
|
|
||||||
let textStyles = tickTextStyles <> [ (Tup.Tuple graphDef.axis1Label graphDef.axisTitleTextStyle)
|
|
||||||
, (Tup.Tuple graphDef.axis2Label graphDef.axisTitleTextStyle)
|
|
||||||
, (Tup.Tuple graphDef.axis3Label graphDef.axisTitleTextStyle)
|
|
||||||
]
|
|
||||||
textDimensions <- getAllTextDimensions dd svgContainer textStyles
|
|
||||||
|
|
||||||
log $ Foldable.foldr (\dim str -> str <> "\n" <> (toString dim.widthPx) <> ", " <> (toString dim.heightPx)) "" $ Map.values textDimensions
|
|
||||||
|
|
||||||
let mySVGErr = ternaryGraph 100.0 50.0 70.0 graphDef textDimensions
|
|
||||||
mySVG <- case mySVGErr of
|
|
||||||
Left error -> throw error
|
|
||||||
Right svg -> pure svg
|
|
||||||
|
|
||||||
svgDocMay <- parseSVGFromString mySVG domParser
|
addUpdateListener dd "axis-title-size"
|
||||||
svgDoc <- case svgDocMay of
|
addUpdateListener dd "tick-label-size"
|
||||||
Left error -> throw error
|
|
||||||
Right doc -> pure doc
|
|
||||||
|
|
||||||
elMay <- firstElementChild $ toParentNode svgDoc
|
addUpdateListener dd "ticks"
|
||||||
svgNode <- case elMay of
|
addUpdateListener dd "tick-size"
|
||||||
Nothing -> throw "no child in svg doc"
|
|
||||||
Just el -> pure $ Element.toNode el
|
|
||||||
|
|
||||||
newNode <- importNode svgNode true dd
|
|
||||||
appendChild newNode svgContainer
|
|
||||||
|
|
||||||
listener <- eventListener update
|
registerInputIncButton dd "axis-1-start" "axis-1-start-up" 1.0
|
||||||
|
registerInputIncButton dd "axis-1-start" "axis-1-start-down" (-1.0)
|
||||||
|
|
||||||
inputElement <- myGetElementById dd "ticks"
|
registerInputIncButton dd "axis-2-start" "axis-2-start-up" 1.0
|
||||||
addEventListener (EventType "input") listener true (Element.toEventTarget inputElement)
|
registerInputIncButton dd "axis-2-start" "axis-2-start-down" (-1.0)
|
||||||
log "20250727T183907"
|
|
||||||
--inputMay <- getElementById "ticks" $ toNonElementParentNode dd
|
registerInputIncButton dd "axis-3-start" "axis-3-start-up" 1.0
|
||||||
--inputNode <- case inputMay of
|
registerInputIncButton dd "axis-3-start" "axis-3-start-down" (-1.0)
|
||||||
--Nothing -> throw $ "Unable to find " <> containerID
|
|
||||||
--Just e -> pure $ Element.toNode e
|
registerInputIncButton dd "axis-title-size" "axis-title-size-up" 1.0
|
||||||
|
registerInputIncButton dd "axis-title-size" "axis-title-size-down" (-1.0)
|
||||||
|
|
||||||
|
registerInputIncButton dd "tick-label-size" "tick-label-size-up" 1.0
|
||||||
|
registerInputIncButton dd "tick-label-size" "tick-label-size-down" (-1.0)
|
||||||
|
|
||||||
|
registerInputIncButton dd "ticks" "ticks-up" 1.0
|
||||||
|
registerInputIncButton dd "ticks" "ticks-down" (-1.0)
|
||||||
|
|
||||||
|
registerInputIncButton dd "tick-size" "tick-size-up" 1.0
|
||||||
|
registerInputIncButton dd "tick-size" "tick-size-down" (-1.0)
|
||||||
|
|
||||||
|
update
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
module TernaryGraph where
|
module TernaryGraph where
|
||||||
|
|
||||||
import Prelude (discard, class Monoid, class Semigroup, Unit, ($), (<=), (<<<), (<>), (*), (+), (-), (/), (&&))
|
import Prelude (discard, class Monoid, class Semigroup, Unit, (==), ($), (<), (<=), (<<<), (<>), (*), (+), (-), (/), (&&))
|
||||||
|
|
||||||
--import Data.Array ((!!), concat, cons, mapWithIndex, range)
|
--import Data.Array ((!!), concat, cons, mapWithIndex, range)
|
||||||
import Data.Array as Array
|
import Data.Array as Array
|
||||||
@@ -18,6 +18,13 @@ import Data.Set as Set
|
|||||||
import Data.Tuple as Tup
|
import Data.Tuple as Tup
|
||||||
import Data.Tuple.Nested (Tuple3, tuple3, get1, get2, get3)
|
import Data.Tuple.Nested (Tuple3, tuple3, get1, get2, get3)
|
||||||
|
|
||||||
|
-- A spatial value (position, size) in pixels
|
||||||
|
-- TODO: Apply this across all spatial values
|
||||||
|
newtype Pixels = Pixels Number
|
||||||
|
|
||||||
|
unpixel :: Pixels -> Number
|
||||||
|
unpixel (Pixels value) = value
|
||||||
|
|
||||||
type Dimension =
|
type Dimension =
|
||||||
{ widthPx :: Number
|
{ widthPx :: Number
|
||||||
, heightPx :: Number
|
, heightPx :: Number
|
||||||
@@ -42,6 +49,7 @@ type GraphDefinition =
|
|||||||
, axis3Start :: Int
|
, axis3Start :: Int
|
||||||
, numTicks :: Int
|
, numTicks :: Int
|
||||||
, tickTextStyle :: TextStyle
|
, tickTextStyle :: TextStyle
|
||||||
|
, tickSize :: Pixels
|
||||||
, axisTitleTextStyle :: TextStyle
|
, axisTitleTextStyle :: TextStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,21 +109,23 @@ svgTextID idMaybe text { x: x, y: y } angle style dimension =
|
|||||||
Maybe.Nothing -> ""
|
Maybe.Nothing -> ""
|
||||||
Maybe.Just id -> "id=\"" <> id <> "\""
|
Maybe.Just id -> "id=\"" <> id <> "\""
|
||||||
|
|
||||||
-- TODO: Make axis tick size a parameter
|
getTick :: Number -> Int -> Pixels -> Int -> Line
|
||||||
getTick :: Number -> Int -> Int -> Line
|
getTick scale numTicks tickSize tickI =
|
||||||
getTick scale numTicks tickI =
|
{start: {x: x, y: -(0.5 + (unpixel tickSize) / scale)}, end: {x: x, y: y}}
|
||||||
{start: {x: x, y: -(0.5 + 5.0 / scale)}, end: {x: x, y: y}}
|
|
||||||
where
|
where
|
||||||
x = 2.0 * (sin (pi / 3.0)) * (Int.toNumber tickI) / (Int.toNumber numTicks) - (sin (pi / 3.0))
|
-- For even number of ticks, the ticks don't intersect in the required pattern, so offset them
|
||||||
y = if tickI <= numTicks / 2
|
x = if (Int.rem numTicks 2) == 0
|
||||||
|
then 2.0 * (sin (pi / 3.0)) * (Int.toNumber tickI) / (Int.toNumber numTicks) - (sin (pi / 3.0))
|
||||||
|
else 2.0 * (sin (pi / 3.0)) * ((Int.toNumber tickI) + 1.0) / ((Int.toNumber numTicks) + 0.5) - (sin (pi / 3.0)) - ((9.5 / 6.0) / ((Int.toNumber numTicks) + 0.5))
|
||||||
|
y = if x < 0.0
|
||||||
then 1.0 + x * 1.5 / (sin (pi / 3.0))
|
then 1.0 + x * 1.5 / (sin (pi / 3.0))
|
||||||
else 1.0 - x * 1.5 / (sin (pi / 3.0))
|
else 1.0 - x * 1.5 / (sin (pi / 3.0))
|
||||||
|
|
||||||
getTicks :: Number -> Number -> Int -> Tuple3 (Array Line) (Array Line) (Array Line)
|
getTicks :: Number -> Number -> Pixels -> Int -> Tuple3 (Array Line) (Array Line) (Array Line)
|
||||||
getTicks scale angle numTicks =
|
getTicks scale angle tickSize numTicks =
|
||||||
tuple3 axis1Lines axis2Lines axis3Lines
|
tuple3 axis1Lines axis2Lines axis3Lines
|
||||||
where
|
where
|
||||||
foo = map (getTick scale numTicks) (Array.range 0 numTicks)
|
foo = map (getTick scale (numTicks - 1) tickSize) (Array.range 0 (numTicks - 1))
|
||||||
axis1Lines = map (rotateLine angle) foo
|
axis1Lines = map (rotateLine angle) foo
|
||||||
axis2Lines = map (rotateLine (2.0 * pi / 3.0)) axis1Lines
|
axis2Lines = map (rotateLine (2.0 * pi / 3.0)) axis1Lines
|
||||||
axis3Lines = map (rotateLine (2.0 * pi / 3.0)) axis2Lines
|
axis3Lines = map (rotateLine (2.0 * pi / 3.0)) axis2Lines
|
||||||
@@ -127,9 +137,9 @@ unfragment (XMLFragment frag) = frag
|
|||||||
ternaryGraphSvg :: Array XMLFragment -> String
|
ternaryGraphSvg :: Array XMLFragment -> String
|
||||||
ternaryGraphSvg fragments = """<?xml version="1.0" encoding="UTF-8"?>
|
ternaryGraphSvg fragments = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg
|
<svg
|
||||||
width="150mm"
|
width="110mm"
|
||||||
height="120mm"
|
height="80mm"
|
||||||
viewBox="-50 -50 200 250"
|
viewBox="-75 -50 250 220"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
@@ -159,7 +169,7 @@ tickLabelStrings def =
|
|||||||
ternaryGraph :: Number -> Number -> Number -> GraphDefinition -> Map.Map (Tup.Tuple String TextStyle) Dimension -> Either.Either String String
|
ternaryGraph :: Number -> Number -> Number -> GraphDefinition -> Map.Map (Tup.Tuple String TextStyle) Dimension -> Either.Either String String
|
||||||
ternaryGraph scale xOffset yOffset definition textDimensions = result
|
ternaryGraph scale xOffset yOffset definition textDimensions = result
|
||||||
where
|
where
|
||||||
axisTickLines = getTicks scale pi definition.numTicks
|
axisTickLines = getTicks scale pi definition.tickSize definition.numTicks
|
||||||
axis1TickLines = map (transformLine scale xOffset yOffset) (get1 axisTickLines)
|
axis1TickLines = map (transformLine scale xOffset yOffset) (get1 axisTickLines)
|
||||||
axis2TickLines = map (transformLine scale xOffset yOffset) (get2 axisTickLines)
|
axis2TickLines = map (transformLine scale xOffset yOffset) (get2 axisTickLines)
|
||||||
axis3TickLines = map (transformLine scale xOffset yOffset) (get3 axisTickLines)
|
axis3TickLines = map (transformLine scale xOffset yOffset) (get3 axisTickLines)
|
||||||
@@ -204,8 +214,13 @@ ternaryGraph scale xOffset yOffset definition textDimensions = result
|
|||||||
axis2TickStarts = map (\line -> line.start + axis2Offset)
|
axis2TickStarts = map (\line -> line.start + axis2Offset)
|
||||||
axis3TickStarts = map (\line -> line.start + axis3Offset)
|
axis3TickStarts = map (\line -> line.start + axis3Offset)
|
||||||
|
|
||||||
axisTickLabels = \rotation startI -> Array.mapWithIndex (\i point ->
|
axisTickLabels = \rotation startI inverted -> Array.mapWithIndex (\i point ->
|
||||||
let text = ("E" <> (toString (Int.toNumber (i + startI))))
|
let index = if inverted
|
||||||
|
then startI + definition.numTicks - 1 - i
|
||||||
|
else startI + i
|
||||||
|
in
|
||||||
|
--let index = direction * (i + startI) in
|
||||||
|
let text = ("E" <> (toString (Int.toNumber index)))
|
||||||
angle = 0.0
|
angle = 0.0
|
||||||
labelText = case Map.lookup (Tup.Tuple text definition.tickTextStyle) textDimensions of
|
labelText = case Map.lookup (Tup.Tuple text definition.tickTextStyle) textDimensions of
|
||||||
Maybe.Nothing -> Either.Left ("Failed to find '" <> text <> "' in dimensions map")
|
Maybe.Nothing -> Either.Left ("Failed to find '" <> text <> "' in dimensions map")
|
||||||
@@ -214,9 +229,9 @@ ternaryGraph scale xOffset yOffset definition textDimensions = result
|
|||||||
)
|
)
|
||||||
|
|
||||||
axis1TickLabels :: Array (Either.Either String XMLFragment)
|
axis1TickLabels :: Array (Either.Either String XMLFragment)
|
||||||
axis1TickLabels = axisTickLabels 0.0 definition.axis1Start $ axis1TickStarts axis1TickLines
|
axis1TickLabels = axisTickLabels 0.0 definition.axis1Start true $ axis1TickStarts axis1TickLines
|
||||||
axis2TickLabels = axisTickLabels 0.0 definition.axis2Start $ axis2TickStarts axis2TickLines
|
axis2TickLabels = axisTickLabels 0.0 definition.axis2Start false $ axis2TickStarts axis2TickLines
|
||||||
axis3TickLabels = axisTickLabels 0.0 definition.axis3Start $ axis3TickStarts axis3TickLines
|
axis3TickLabels = axisTickLabels 0.0 definition.axis3Start true $ axis3TickStarts axis3TickLines
|
||||||
|
|
||||||
labelFragmentsErr = Array.concat [axisTitlesSvg, axis1TickLabels, axis2TickLabels, axis3TickLabels]
|
labelFragmentsErr = Array.concat [axisTitlesSvg, axis1TickLabels, axis2TickLabels, axis3TickLabels]
|
||||||
|
|
||||||
|
|||||||
129
style.css
Normal file
129
style.css
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
.avatar {
|
||||||
|
width: 2em;
|
||||||
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Liberation sans, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 3px;
|
||||||
|
border-color: #f97200;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: auto auto;
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-content {
|
||||||
|
padding: 0em 1em 1em 1em;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 100%;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
width: fit-content;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0.1em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0.1em;
|
||||||
|
color: #707070;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
border-top: 3px solid #c0c0c0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: center;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th, table td {
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #c0c0c0;
|
||||||
|
border-width: 3px;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:nth-child(odd) {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:nth-child(odd) {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-wrapper {
|
||||||
|
height: 3em;
|
||||||
|
width: 5em;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: black;
|
||||||
|
border-width: 1px;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-wrapper:hover {
|
||||||
|
border-color: #3C7FB1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
#breadcrumb {
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#breadcrumb a {
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#breadcrumb a :hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underlined {
|
||||||
|
text-decoration-line: underline;
|
||||||
|
text-decoration-style: dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#site-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
61
ternary-graph.css
Normal file
61
ternary-graph.css
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
button {
|
||||||
|
border-width: 3px;
|
||||||
|
border-bottom-color: #B3B3B3;
|
||||||
|
border-right-color: #B3B3B3;
|
||||||
|
border-left-color: #E3E3E3;
|
||||||
|
border-top-color: #E3E3E3;
|
||||||
|
height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border-width: 3px;
|
||||||
|
border-bottom-color: #E3E3E3;
|
||||||
|
border-right-color: #E3E3E3;
|
||||||
|
border-left-color: #B3B3B3;
|
||||||
|
border-top-color: #B3B3B3;
|
||||||
|
height: 2em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-content {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: auto;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#svg-container {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user