Compare commits

..

10 Commits

Author SHA1 Message Date
Nathan McRae
4394d26184 Reverse axes 1 and 3
Because we want axis 1 to increase to the right, and axis 3 to increase
up
2025-09-03 21:40:39 -07:00
Nathan McRae
9859938df7 Swap typefaces 2025-09-03 21:27:27 -07:00
Nathan McRae
62fefb0cb1 Fix some alignment 2025-09-03 21:27:09 -07:00
Nathan McRae
6a4a0f5719 Add download button 2025-08-27 21:15:20 -07:00
Nathan McRae
5e795ac9f3 Fix alignment of even number of ticks
Without this, it created a hexagram type shape in the graph and the tick
marks didn't all intersect together at one point.
2025-08-24 19:03:26 -07:00
Nathan McRae
da1e6f32a5 Initial styling
Change from default increment/decrement buttons to custom ones since
that's easier to style.
2025-08-24 13:32:12 -07:00
Nathan McRae
6541d65f3a Add other graph parameters to interface 2025-08-20 22:55:11 -07:00
Nathan McRae
aa7b0813c4 Simplify adding event listeners 2025-08-20 22:42:20 -07:00
Nathan McRae
5b4cf834a0 Remove redundant code from main 2025-08-20 22:19:14 -07:00
Nathan McRae
2a4b5702af Add tick size control 2025-08-20 21:49:23 -07:00
6 changed files with 450 additions and 89 deletions

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"
addUpdateListener dd "axis-2-title"
addUpdateListener dd "axis-2-start"
addUpdateListener dd "axis-3-title"
addUpdateListener dd "axis-3-start"
let graphDef = { axis1Label: "axis 1" addUpdateListener dd "axis-title-size"
, axis2Label: "axis 2" addUpdateListener dd "tick-label-size"
, 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 addUpdateListener dd "ticks"
addUpdateListener dd "tick-size"
log $ Array.intercalate ",\n" (Set.toUnfoldable tickText) registerInputIncButton dd "axis-1-start" "axis-1-start-up" 1.0
registerInputIncButton dd "axis-1-start" "axis-1-start-down" (-1.0)
let tickTextStyles = Foldable.foldr (\text textStyleArray -> Array.cons (Tup.Tuple text graphDef.tickTextStyle) textStyleArray) [] tickText registerInputIncButton dd "axis-2-start" "axis-2-start-up" 1.0
let textStyles = tickTextStyles <> [ (Tup.Tuple graphDef.axis1Label graphDef.axisTitleTextStyle) registerInputIncButton dd "axis-2-start" "axis-2-start-down" (-1.0)
, (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 registerInputIncButton dd "axis-3-start" "axis-3-start-up" 1.0
registerInputIncButton dd "axis-3-start" "axis-3-start-down" (-1.0)
let mySVGErr = ternaryGraph 100.0 50.0 70.0 graphDef textDimensions registerInputIncButton dd "axis-title-size" "axis-title-size-up" 1.0
mySVG <- case mySVGErr of registerInputIncButton dd "axis-title-size" "axis-title-size-down" (-1.0)
Left error -> throw error
Right svg -> pure svg
svgDocMay <- parseSVGFromString mySVG domParser registerInputIncButton dd "tick-label-size" "tick-label-size-up" 1.0
svgDoc <- case svgDocMay of registerInputIncButton dd "tick-label-size" "tick-label-size-down" (-1.0)
Left error -> throw error
Right doc -> pure doc
elMay <- firstElementChild $ toParentNode svgDoc registerInputIncButton dd "ticks" "ticks-up" 1.0
svgNode <- case elMay of registerInputIncButton dd "ticks" "ticks-down" (-1.0)
Nothing -> throw "no child in svg doc"
Just el -> pure $ Element.toNode el
newNode <- importNode svgNode true dd registerInputIncButton dd "tick-size" "tick-size-up" 1.0
appendChild newNode svgContainer registerInputIncButton dd "tick-size" "tick-size-down" (-1.0)
listener <- eventListener update update
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

View File

@@ -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
View 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
View 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%
}