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>
|
||||
<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>
|
||||
<body>
|
||||
ticks:
|
||||
<input type="number" id="ticks"/>
|
||||
<div id="svg-container" class="my-class"></div>
|
||||
<div id="breadcrumb">
|
||||
<a id="site-title" href="https://nathanmcrae.name">
|
||||
<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>
|
||||
<script src="index.js"></script>
|
||||
</html>
|
||||
@@ -19,6 +19,7 @@ to generate this file without the comments in this block.
|
||||
, "exceptions"
|
||||
, "foldable-traversable"
|
||||
, "integers"
|
||||
, "js-uri"
|
||||
, "lists"
|
||||
, "maybe"
|
||||
, "numbers"
|
||||
|
||||
202
src/Main.purs
202
src/Main.purs
@@ -15,7 +15,9 @@ import Effect (Effect)
|
||||
import Effect.Console (log)
|
||||
import Effect.Class (liftEffect)
|
||||
import Effect.Exception (throw)
|
||||
import JSURI (encodeURIComponent)
|
||||
import TernaryGraph (Dimension, svgTextID, ternaryGraph, ternaryGraphSvg, TextStyle, tickLabelStrings)
|
||||
import TernaryGraph as TernaryGraph
|
||||
import Web.DOM.Document (contentType
|
||||
, createElement
|
||||
, Document
|
||||
@@ -104,35 +106,92 @@ getAllTextDimensions document svgContainer strings = do
|
||||
|
||||
pure myMap
|
||||
|
||||
update :: Event -> Effect Unit
|
||||
update e = do
|
||||
updateEvent :: Event -> Effect Unit
|
||||
updateEvent _ = do update
|
||||
|
||||
update :: Effect Unit
|
||||
update = do
|
||||
w <- window
|
||||
d <- document w
|
||||
let document = HTMLDoc.toDocument d
|
||||
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"
|
||||
inputHTMLElement <- case HTMLInput.fromElement inputElement of
|
||||
Nothing -> throw "'ticks' element is not an input tag"
|
||||
Just e -> pure e
|
||||
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"
|
||||
|
||||
let graphDef = { axis1Label: "axis 1"
|
||||
, axis2Label: "axis 2"
|
||||
, axis3Label: "axis 3"
|
||||
, axis1Start: 0
|
||||
, axis2Start: 1
|
||||
, axis3Start: 20
|
||||
let graphDef = { axis1Label: axis1Title
|
||||
, axis2Label: axis2Title
|
||||
, axis3Label: axis3Title
|
||||
, axis1Start: axis1Start
|
||||
, axis2Start: axis2Start
|
||||
, axis3Start: axis3Start
|
||||
, numTicks: ticks
|
||||
, tickTextStyle: { sizePx: 12.0
|
||||
, typeface: "Liberation Sans"
|
||||
}
|
||||
, axisTitleTextStyle: { sizePx: 16.0
|
||||
, tickTextStyle: { sizePx: tickLabelSize
|
||||
, typeface: "Liberation Mono"
|
||||
}
|
||||
, tickSize: TernaryGraph.Pixels tickSize
|
||||
, axisTitleTextStyle: { sizePx: axisTitleSize
|
||||
, typeface: "Liberation Sans"
|
||||
}
|
||||
}
|
||||
|
||||
let tickText = tickLabelStrings graphDef
|
||||
@@ -170,67 +229,76 @@ update e = do
|
||||
newNode <- importNode svgNode true document
|
||||
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 = do
|
||||
w <- window
|
||||
d <- document w
|
||||
let dd = HTMLDoc.toDocument d
|
||||
domParser <- makeDOMParser
|
||||
|
||||
svgContainer <- getNodeById dd "svg-container"
|
||||
|
||||
let graphDef = { axis1Label: "axis 1"
|
||||
, axis2Label: "axis 2"
|
||||
, axis3Label: "axis 3"
|
||||
, axis1Start: 0
|
||||
, 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
|
||||
addUpdateListener dd "axis-1-title"
|
||||
addUpdateListener dd "axis-1-start"
|
||||
addUpdateListener dd "axis-2-title"
|
||||
addUpdateListener dd "axis-2-start"
|
||||
addUpdateListener dd "axis-3-title"
|
||||
addUpdateListener dd "axis-3-start"
|
||||
|
||||
svgDocMay <- parseSVGFromString mySVG domParser
|
||||
svgDoc <- case svgDocMay of
|
||||
Left error -> throw error
|
||||
Right doc -> pure doc
|
||||
addUpdateListener dd "axis-title-size"
|
||||
addUpdateListener dd "tick-label-size"
|
||||
|
||||
elMay <- firstElementChild $ toParentNode svgDoc
|
||||
svgNode <- case elMay of
|
||||
Nothing -> throw "no child in svg doc"
|
||||
Just el -> pure $ Element.toNode el
|
||||
|
||||
newNode <- importNode svgNode true dd
|
||||
appendChild newNode svgContainer
|
||||
addUpdateListener dd "ticks"
|
||||
addUpdateListener dd "tick-size"
|
||||
|
||||
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"
|
||||
addEventListener (EventType "input") listener true (Element.toEventTarget inputElement)
|
||||
log "20250727T183907"
|
||||
--inputMay <- getElementById "ticks" $ toNonElementParentNode dd
|
||||
--inputNode <- case inputMay of
|
||||
--Nothing -> throw $ "Unable to find " <> containerID
|
||||
--Just e -> pure $ Element.toNode e
|
||||
registerInputIncButton dd "axis-2-start" "axis-2-start-up" 1.0
|
||||
registerInputIncButton dd "axis-2-start" "axis-2-start-down" (-1.0)
|
||||
|
||||
registerInputIncButton dd "axis-3-start" "axis-3-start-up" 1.0
|
||||
registerInputIncButton dd "axis-3-start" "axis-3-start-down" (-1.0)
|
||||
|
||||
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
|
||||
|
||||
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 as Array
|
||||
@@ -18,6 +18,13 @@ import Data.Set as Set
|
||||
import Data.Tuple as Tup
|
||||
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 =
|
||||
{ widthPx :: Number
|
||||
, heightPx :: Number
|
||||
@@ -42,6 +49,7 @@ type GraphDefinition =
|
||||
, axis3Start :: Int
|
||||
, numTicks :: Int
|
||||
, tickTextStyle :: TextStyle
|
||||
, tickSize :: Pixels
|
||||
, axisTitleTextStyle :: TextStyle
|
||||
}
|
||||
|
||||
@@ -101,21 +109,23 @@ svgTextID idMaybe text { x: x, y: y } angle style dimension =
|
||||
Maybe.Nothing -> ""
|
||||
Maybe.Just id -> "id=\"" <> id <> "\""
|
||||
|
||||
-- TODO: Make axis tick size a parameter
|
||||
getTick :: Number -> Int -> Int -> Line
|
||||
getTick scale numTicks tickI =
|
||||
{start: {x: x, y: -(0.5 + 5.0 / scale)}, end: {x: x, y: y}}
|
||||
getTick :: Number -> Int -> Pixels -> Int -> Line
|
||||
getTick scale numTicks tickSize tickI =
|
||||
{start: {x: x, y: -(0.5 + (unpixel tickSize) / scale)}, end: {x: x, y: y}}
|
||||
where
|
||||
x = 2.0 * (sin (pi / 3.0)) * (Int.toNumber tickI) / (Int.toNumber numTicks) - (sin (pi / 3.0))
|
||||
y = if tickI <= numTicks / 2
|
||||
-- For even number of ticks, the ticks don't intersect in the required pattern, so offset them
|
||||
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))
|
||||
else 1.0 - x * 1.5 / (sin (pi / 3.0))
|
||||
|
||||
getTicks :: Number -> Number -> Int -> Tuple3 (Array Line) (Array Line) (Array Line)
|
||||
getTicks scale angle numTicks =
|
||||
getTicks :: Number -> Number -> Pixels -> Int -> Tuple3 (Array Line) (Array Line) (Array Line)
|
||||
getTicks scale angle tickSize numTicks =
|
||||
tuple3 axis1Lines axis2Lines axis3Lines
|
||||
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
|
||||
axis2Lines = map (rotateLine (2.0 * pi / 3.0)) axis1Lines
|
||||
axis3Lines = map (rotateLine (2.0 * pi / 3.0)) axis2Lines
|
||||
@@ -127,9 +137,9 @@ unfragment (XMLFragment frag) = frag
|
||||
ternaryGraphSvg :: Array XMLFragment -> String
|
||||
ternaryGraphSvg fragments = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg
|
||||
width="150mm"
|
||||
height="120mm"
|
||||
viewBox="-50 -50 200 250"
|
||||
width="110mm"
|
||||
height="80mm"
|
||||
viewBox="-75 -50 250 220"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
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 scale xOffset yOffset definition textDimensions = result
|
||||
where
|
||||
axisTickLines = getTicks scale pi definition.numTicks
|
||||
axisTickLines = getTicks scale pi definition.tickSize definition.numTicks
|
||||
axis1TickLines = map (transformLine scale xOffset yOffset) (get1 axisTickLines)
|
||||
axis2TickLines = map (transformLine scale xOffset yOffset) (get2 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)
|
||||
axis3TickStarts = map (\line -> line.start + axis3Offset)
|
||||
|
||||
axisTickLabels = \rotation startI -> Array.mapWithIndex (\i point ->
|
||||
let text = ("E" <> (toString (Int.toNumber (i + startI))))
|
||||
axisTickLabels = \rotation startI inverted -> Array.mapWithIndex (\i point ->
|
||||
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
|
||||
labelText = case Map.lookup (Tup.Tuple text definition.tickTextStyle) textDimensions of
|
||||
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 = axisTickLabels 0.0 definition.axis1Start $ axis1TickStarts axis1TickLines
|
||||
axis2TickLabels = axisTickLabels 0.0 definition.axis2Start $ axis2TickStarts axis2TickLines
|
||||
axis3TickLabels = axisTickLabels 0.0 definition.axis3Start $ axis3TickStarts axis3TickLines
|
||||
axis1TickLabels = axisTickLabels 0.0 definition.axis1Start true $ axis1TickStarts axis1TickLines
|
||||
axis2TickLabels = axisTickLabels 0.0 definition.axis2Start false $ axis2TickStarts axis2TickLines
|
||||
axis3TickLabels = axisTickLabels 0.0 definition.axis3Start true $ axis3TickStarts axis3TickLines
|
||||
|
||||
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