Commit 5a03d3dd authored by David Davó's avatar David Davó

Selection widget demo

parent d26ecb02
......@@ -10,138 +10,527 @@
"+ RadioButtons\n",
"+ ToggleButtons\n",
"+ Select\n",
"+ SelectMultiple"
"+ SelectMultiple\n",
"+ SelectionSlider\n",
"+ SelectionRangeSlider"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"These widgets can be used to choose between multiple alternatives. The `SelectMultiple` widget allows multiple selections, whereas `Dropdown`, `RadioButtons`, `ToggleButtons`, and `Select` only allow one selection."
"These widgets can be used to choose between multiple alternatives. The `SelectMultiple` widget allows multiple selections, whereas `Dropdown`, `RadioButtons`, `ToggleButtons`, `SelectionSlider` and `Select` only allow one selection. `SelectionRangeSlider` returns exactly two selections.\n",
"\n",
"Every widget, except the sliders, can return `Nothing` as it selected index."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"metadata": {
"collapsed": true
"tags": []
},
"outputs": [],
"outputs": [
{
"data": {
"text/html": [
"<style>/* Styles used for the Hoogle display in the pager */\n",
".hoogle-doc {\n",
"display: block;\n",
"padding-bottom: 1.3em;\n",
"padding-left: 0.4em;\n",
"}\n",
".hoogle-code {\n",
"display: block;\n",
"font-family: monospace;\n",
"white-space: pre;\n",
"}\n",
".hoogle-text {\n",
"display: block;\n",
"}\n",
".hoogle-name {\n",
"color: green;\n",
"font-weight: bold;\n",
"}\n",
".hoogle-head {\n",
"font-weight: bold;\n",
"}\n",
".hoogle-sub {\n",
"display: block;\n",
"margin-left: 0.4em;\n",
"}\n",
".hoogle-package {\n",
"font-weight: bold;\n",
"font-style: italic;\n",
"}\n",
".hoogle-module {\n",
"font-weight: bold;\n",
"}\n",
".hoogle-class {\n",
"font-weight: bold;\n",
"}\n",
".get-type {\n",
"color: green;\n",
"font-weight: bold;\n",
"font-family: monospace;\n",
"display: block;\n",
"white-space: pre-wrap;\n",
"}\n",
".show-type {\n",
"color: green;\n",
"font-weight: bold;\n",
"font-family: monospace;\n",
"margin-left: 1em;\n",
"}\n",
".mono {\n",
"font-family: monospace;\n",
"display: block;\n",
"}\n",
".err-msg {\n",
"color: red;\n",
"font-style: italic;\n",
"font-family: monospace;\n",
"white-space: pre;\n",
"display: block;\n",
"}\n",
"#unshowable {\n",
"color: red;\n",
"font-weight: bold;\n",
"}\n",
".err-msg.in.collapse {\n",
"padding-top: 0.7em;\n",
"}\n",
".highlight-code {\n",
"white-space: pre;\n",
"font-family: monospace;\n",
"}\n",
".suggestion-warning { \n",
"font-weight: bold;\n",
"color: rgb(200, 130, 0);\n",
"}\n",
".suggestion-error { \n",
"font-weight: bold;\n",
"color: red;\n",
"}\n",
".suggestion-name {\n",
"font-weight: bold;\n",
"}\n",
"</style><div class=\"suggestion-name\" style=\"clear:both;\">Unused LANGUAGE pragma</div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Found:</div><div class=\"highlight-code\" id=\"haskell\">{-# LANGUAGE OverloadedStrings #-}</div></div><div class=\"suggestion-row\" style=\"float: left;\"><div class=\"suggestion-warning\">Why Not:</div><div class=\"highlight-code\" id=\"haskell\"></div></div>"
],
"text/plain": [
"Line 1: Unused LANGUAGE pragma\n",
"Found:\n",
"{-# LANGUAGE OverloadedStrings #-}\n",
"Why not:"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"{-# LANGUAGE OverloadedStrings #-}\n",
"{-# LANGUAGE FlexibleContexts #-}\n",
"import IHaskell.Display.Widgets"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"metadata": {
"collapsed": false
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"-- Allows single selection\n",
"tgbs <- mkToggleButtons\n",
"dropdown <- mkDropdown\n",
"radio <- mkRadioButtons\n",
"select <- mkSelect\n",
"slider <- mkSelectionSlider\n",
"\n",
"-- Allows multiple selections\n",
"msel <- mkSelectMultiple"
"msel <- mkSelectMultiple\n",
"rslider <- mkSelectionRangeSlider"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Single Selection Widgets\n",
"\n",
"We can set the options with `Options $ OptionLabels [array]`. Let's see how can we select one of two functions with multiple selectors:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "79ef5a0e-de6e-4374-8bad-4948deaffb05",
"version_major": 2,
"version_minor": 0
}
},
"outputs": [],
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "bb697874-4d8c-4841-af02-0ef2984df7b1",
"version_major": 2,
"version_minor": 0
}
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "279a451a-6528-47b7-aca3-07d42be0e57b",
"version_major": 2,
"version_minor": 0
}
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "72511b23-5bb3-4e25-85d7-ba0bae5f8a0d",
"version_major": 2,
"version_minor": 0
}
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "131ab0e7-51c9-41ce-bc7c-7f31de28c38b",
"version_major": 2,
"version_minor": 0
}
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"setField msel Description \"Functions to show (One or more)\"\n",
"setField msel Options (OptionLabels [\"sin\", \"cos\"])\n",
"\n",
"setField tgbs Description \"Plot style\"\n",
"setField tgbs Options (OptionLabels [\"line\", \"point\"])"
"init w = do\n",
" setField w Description \"Function:\"\n",
" setField w Options (OptionLabels [\"sin\", \"cos\"])\n",
" return w\n",
" \n",
"init tgbs\n",
"init dropdown\n",
"init radio\n",
"init select\n",
"init slider"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The cell below requires `Chart` and `Chart-cairo` to be installed."
"We can create a `SelectionHandler` function that will be run every time the value is changed. Let's synchronize them so all the selectors always display the same value."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false,
"scrolled": true
},
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"import Graphics.Rendering.Chart.Easy hiding (tan)\n",
"import Graphics.Rendering.Chart.Backend.Cairo\n",
"import qualified Data.ByteString as B\n",
"import Data.Text (pack, unpack)\n",
"import IHaskell.Display (base64)\n",
"import Control.Applicative ((<$>))\n",
"\n",
"import Control.Monad (when, forM)\n",
"import Data.Maybe (fromJust)\n",
"\n",
"dset :: [(String, [(Double, Double)])]\n",
"dset = [(\"sin\", zmap sin r), (\"cos\", zmap cos r)]\n",
" where zmap f xs = zip xs (map f xs)\n",
" r = [0, 0.1 .. 6.3]\n",
"\n",
"i <- mkImageWidget\n",
"setField i Width 500\n",
"setField i Height 500\n",
"\n",
"-- Redraw the plot based on values from the widgets\n",
"refresh = do\n",
" -- Read values from the widgets\n",
" funs <- map unpack <$> getField msel SelectedValues\n",
" sty <- unpack <$> getField tgbs SelectedValue\n",
"setHandlerOpt w = setField w SelectionHandler $ do\n",
" y <- getField w OptionalIndex\n",
" case y of\n",
" Just x -> do\n",
" setField dropdown OptionalIndex $ Just x\n",
" setField tgbs OptionalIndex $ Just x\n",
" setField radio OptionalIndex $ Just x\n",
" setField select OptionalIndex $ Just x\n",
" setField slider Index x\n",
" _ -> return ()\n",
" \n",
" let pts = zip funs (map (fromJust . flip lookup dset) funs)\n",
" opts = def { _fo_size = (500, 500) }\n",
" toFile opts \".chart\" $ do\n",
" layout_title .= \"Plotting: \" ++ unwords funs\n",
" if sty == \"line\"\n",
" then mapM_ (\\(s, ps) -> plot (line s [ps])) pts\n",
" else mapM_ (\\(s, ps) -> plot (points s ps)) pts\n",
"\n",
" img <- B.readFile \".chart\"\n",
" setField i B64Value (base64 img)\n",
"setHandler w = setField w SelectionHandler $ do\n",
" x <- getField w Index\n",
" setField dropdown OptionalIndex $ Just x\n",
" setField tgbs OptionalIndex $ Just x\n",
" setField radio OptionalIndex $ Just x\n",
" setField select OptionalIndex $ Just x\n",
" setField slider Index x\n",
" \n",
"-- Add event handlers to make widgets work\n",
"setField msel SelectionHandler refresh\n",
"setField tgbs SelectionHandler refresh\n",
"\n",
"-- Trigger event to show empty grid initially\n",
"triggerSelection msel"
"setHandlerOpt tgbs\n",
"setHandlerOpt dropdown\n",
"setHandlerOpt radio\n",
"setHandlerOpt select\n",
"setHandler slider"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Multiple Selection Widgets\n",
"\n",
"In the multiple selection widget, you can select multiple items by doing Shift+Click. Let's do an example for selecting provinces (maybe for filtering later)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "615fb6a1-85b1-4cfc-9621-3f7f74ac46a3",
"version_major": 2,
"version_minor": 0
}
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ccaa = \n",
" [ \"Andalusia\", \"Aragon\", \"Asturias\", \"Balearic Islands\", \"Basque Country\"\n",
" , \"Canary Islands\", \"Cantabria\", \"Castile and León\", \"Castilla-La Mancha\", \"Catalonia\"\n",
" , \"Madrid\", \"Extremadura\", \"Galicia\", \"La Rioja\", \"Navarre\", \"Murcia\", \"Valencia\"\n",
" ]\n",
" \n",
"setField msel Options (OptionLabels ccaa)\n",
"msel"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also set the items selected with the `Indices` attribute (which is an Integer list)."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"-- Display the widgets\n",
"msel\n",
"tgbs\n",
"i"
"setField msel Indices [0,2]\n",
"setField msel Indices [-1,3]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `Dropdown`, `RadioButtons` and `Select` widgets behave just like the `ToggleButtons` widget. They have the same properties, and the same functionality."
"But maybe we to scroll a lot because we don't have enough space, which is a bit tiring. We can fix this setting the `Rows` attribute. If we set it to `Nothing`, we are letting the frontend decide the number of rows."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"setField msel Rows $ Just 10"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's create a slider. This one also returns an array of `Indices`, but it's always of size 2."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "35e31cd5-834c-4357-9926-bad42f4a0f88",
"version_major": 2,
"version_minor": 0
}
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"setField rslider Options (OptionLabels [\"Very bad\", \"Bad\", \"Regular\", \"Good\", \"Very good\"])\n",
"rslider"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As expected, we can get/set the selected values."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[2,4]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"getField rslider Indices\n",
"setField rslider Indices [1,3]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
......@@ -153,10 +542,12 @@
"language_info": {
"codemirror_mode": "ihaskell",
"file_extension": ".hs",
"mimetype": "text/x-haskell",
"name": "haskell",
"version": "7.10.2"
"pygments_lexer": "Haskell",
"version": "8.10.4"
}
},
"nbformat": 4,
"nbformat_minor": 0
"nbformat_minor": 4
}
......@@ -41,7 +41,7 @@ mkSelectionRangeSlider = do
:& (ReadOut =:: True)
:& (ContinuousUpdate =:: True)
:& RNil
widgetState = WidgetState $ rput (Indices =:: [0,0]) $ selectionAttrs <+> selectionRangeSliderAttrs
widgetState = WidgetState $ rput (Indices =:. ([0,0], rangeSliderVerification)) $ selectionAttrs <+> selectionRangeSliderAttrs
stateIO <- newIORef widgetState
......
......@@ -128,7 +128,7 @@ type CoreWidgetClass = ['S.ViewModule, 'S.ViewModuleVersion, 'S.ModelModule, 'S.
type DOMWidgetClass = ['S.ModelName, 'S.ViewName, 'S.DOMClasses, 'S.Tabbable, 'S.Tooltip, 'S.DisplayHandler] -- TODO: Add layout
type DescriptionWidgetClass = CoreWidgetClass ++ DOMWidgetClass :++ '[ 'S.Description ]
type DescriptionWidgetClass = CoreWidgetClass :++ DOMWidgetClass :++ '[ 'S.Description ]
type StringClass = DescriptionWidgetClass :++ ['S.StringValue, 'S.Placeholder]
......@@ -323,7 +323,7 @@ type family WidgetFields (w :: WidgetType) :: [Field] where
WidgetFields 'SelectionRangeSliderType = MultipleSelectionClass :++ '[ 'S.Orientation, 'S.ReadOut, 'S.ContinuousUpdate ]
WidgetFields 'ToggleButtonsType =
SelectionClass :++ ['S.Tooltips, 'S.Icons, 'S.ButtonStyle]
WidgetFields 'SelectMultipleType = MultipleSelectionClass ++ '[ S.Rows ]
WidgetFields 'SelectMultipleType = MultipleSelectionClass :++ '[ 'S.Rows ]
WidgetFields 'IntTextType = IntClass :++ [ 'S.Disabled, 'S.ContinuousUpdate, 'S.StepInt ]
WidgetFields 'BoundedIntTextType = BoundedIntClass :++ [ 'S.Disabled, 'S.ContinuousUpdate, 'S.StepInt ]
WidgetFields 'IntSliderType =
......@@ -587,6 +587,10 @@ instance ToPairs (Attr 'S.Rows) where
(=::) :: (SingI f, Typeable (FieldType f)) => Sing f -> FieldType f -> Attr f
s =:: x = Attr { _value = Real x, _verify = return, _field = reflect s }
-- | Store the value for a field, with a custom verification
(=:.) :: (SingI f, Typeable (FieldType f)) => Sing f -> (FieldType f, FieldType f -> IO (FieldType f) ) -> Attr f
s =:. (x,v) = Attr { _value = Real x, _verify = v, _field = reflect s }
-- | If the number is in the range, return it. Otherwise raise the appropriate (over/under)flow
-- exception.
rangeCheck :: (Num a, Ord a) => (a, a) -> a -> IO a
......@@ -596,6 +600,12 @@ rangeCheck (l, u) x
| u < x = Ex.throw Ex.Overflow
| otherwise = error "The impossible happened in IHaskell.Display.Widgets.Types.rangeCheck"
rangeSliderVerification :: [Integer] -> IO [Integer]
rangeSliderVerification xs@[a,b]
| a <= b = return xs
| otherwise = Ex.throw $ Ex.AssertionFailed "The first index should be smaller than the second"
rangeSliderVerification _ = Ex.throw $ Ex.AssertionFailed "There should be two indices"
-- | Store a numeric value, with verification mechanism for its range.
ranged :: (SingI f, Num (FieldType f), Ord (FieldType f), Typeable (FieldType f))
=> Sing f -> (FieldType f, FieldType f) -> AttrVal (FieldType f) -> Attr f
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment