Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TimePicker from Bokeh #7013

Merged
merged 9 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions examples/reference/widgets/TimePicker.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import datetime as dt\n",
"import panel as pn\n",
"\n",
"pn.extension()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ``TimePicker`` widget allows entering a time value as text or `datetime.time`. \n",
"\n",
"Discover more on using widgets to add interactivity to your applications in the [how-to guides on interactivity](../how_to/interactivity/index.md). Alternatively, learn [how to set up callbacks and (JS-)links between parameters](../../how_to/links/index.md) or [how to use them as part of declarative UIs with Param](../../how_to/param/index.html).\n",
"\n",
"#### Parameters:\n",
"\n",
"For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n",
"\n",
"##### Core\n",
"\n",
"* **``value``** (str | datetime.time): The current value\n",
"* **``start``** (str | datetime.time): Inclusive lower bound of the allowed time selection\n",
"* **``end``** (str | datetime.time): Inclusive upper bound of the allowed time selection\n",
"\n",
" ```\n",
" +---+------------------------------------+------------+\n",
" | H | Hours (24 hours) | 00 to 23 |\n",
" | h | Hours | 1 to 12 |\n",
" | G | Hours, 2 digits with leading zeros | 1 to 12 |\n",
" | i | Minutes | 00 to 59 |\n",
" | S | Seconds, 2 digits | 00 to 59 |\n",
" | s | Seconds | 0, 1 to 59 |\n",
" | K | AM/PM | AM or PM |\n",
" +---+------------------------------------+------------+\n",
" ```\n",
" See also https://flatpickr.js.org/formatting/#date-formatting-tokens.\n",
"\n",
"\n",
"\n",
"##### Display\n",
"\n",
"* **``disabled``** (boolean): Whether the widget is editable\n",
"* **``name``** (str): The title of the widget\n",
"* **``format``** (str): Formatting specification for the display of the picked date.\n",
"* **``hour_increment``** (int): Defines the granularity of hour value increments in the UI. Default is 1.\n",
"* **``minute_increment``** (int): Defines the granularity of minute value increments in the UI. Default is 1.\n",
"* **``second_increment``** (int): Defines the granularity of second value increments in the UI. Default is 1.\n",
"* **``seconds``** (bool): Allows to select seconds. By default, only hours and minutes are selectable, and AM/PM depending on the `clock` option. Default is False.\n",
"* **``military_time``** (bool): Whether to display time in 24-hour format. Default is True.\n",
"\n",
"___"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `TimePicker` widget allows selecting a time of day."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"time_picker = pn.widgets.TimePicker(name='Time Picker', value=dt.datetime.now().time(), format='H:i K')\n",
"time_picker"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Either `datetime.time` or `str` can be used as input and `TimePicker` can be bounded by a start and end time."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"time_picker = pn.widgets.TimePicker(name='Time Picker', value=\"08:28\", start='00:00', end='12:00')\n",
"time_picker"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
1 change: 1 addition & 0 deletions panel/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .markup import HTML, JSON, PDF # noqa
from .reactive_html import ReactiveHTML # noqa
from .state import State # noqa
from .time_picker import TimePicker # noqa
from .trend import TrendIndicator # noqa
from .widgets import ( # noqa
Audio, Button, CheckboxButtonGroup, CustomMultiSelect, CustomSelect,
Expand Down
1 change: 1 addition & 0 deletions panel/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export {Terminal} from "./terminal"
export {TextAreaInput} from "./textarea_input"
export {TextInput} from "./text_input"
export {TextToSpeech} from "./text_to_speech"
export {TimePicker} from "./time_picker"
export {ToggleIcon} from "./toggle_icon"
export {TooltipIcon} from "./tooltip_icon"
export {TrendIndicator} from "./trend"
Expand Down
7 changes: 7 additions & 0 deletions panel/models/time_picker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from bokeh.models import TimePicker as BkTimePicker


class TimePicker(BkTimePicker):
"""
A custom Panel version of the Bokeh TimePicker model which fixes timezones.
"""
52 changes: 52 additions & 0 deletions panel/models/time_picker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {TimePicker as BkTimePicker, TimePickerView as BkTimePickerView} from "@bokehjs/models/widgets/time_picker"
import type * as p from "@bokehjs/core/properties"



export class TimePickerView extends BkTimePickerView {
declare model: TimePicker

// Override the render method or any method where time rendering is done
override render(): void {
// Fix time zone difference; add back 8 hours
const date = this.model.value ? new Date(this.model.value) : new Date();
date.setHours(date.getHours() + 8);
ahuang11 marked this conversation as resolved.
Show resolved Hide resolved

console.log(date);

// get type of date
console.log(this.model.value)
console.log("Type of Date:", typeof this.model.value);

console.log("Converted Date:", date.toISOString());
console.log("Converted Time:", date.getTime());
this.model.value = 72000000
ahuang11 marked this conversation as resolved.
Show resolved Hide resolved

super.render();
}
}

export namespace TimePicker {
export type Attrs = p.AttrsOf<Props>
export type Props = BkTimePicker.Props & {
}
}

export interface TimePicker extends TimePicker.Attrs { }

export class TimePicker extends BkTimePicker {
declare properties: TimePicker.Props

constructor(attrs?: Partial<TimePicker.Attrs>) {
super(attrs)
}

static override __module__ = "panel.models.time_picker"

static {
this.prototype.default_view = TimePickerView

this.define<TimePicker.Props>(({}) => ({
}))
}
}
20 changes: 18 additions & 2 deletions panel/tests/widgets/test_input.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import date, datetime
from datetime import date, datetime, time as dt_time
from pathlib import Path

import numpy as np
Expand All @@ -10,7 +10,7 @@
from panel.widgets import (
ArrayInput, Checkbox, DatePicker, DateRangePicker, DatetimeInput,
DatetimePicker, DatetimeRangeInput, DatetimeRangePicker, FileInput,
FloatInput, IntInput, LiteralInput, StaticText, TextInput,
FloatInput, IntInput, LiteralInput, StaticText, TextInput, TimePicker,
)


Expand Down Expand Up @@ -185,6 +185,22 @@ def test_datetime_range_picker(document, comm):
datetime_range_picker._process_events({'value': '2018-09-10 00:00:01'})


def test_time_picker(document, comm):
time_picker = TimePicker(name='Time Picker', value=dt_time(hour=18), format='H:i K')
assert time_picker.value == dt_time(hour=18)
assert time_picker.format == 'H:i K'
assert time_picker.start is None
assert time_picker.end is None


def test_time_picker_str(document, comm):
time_picker = TimePicker(name='Time Picker', value="08:28", start='00:00', end='12:00')
assert time_picker.value == "08:28"
assert time_picker.format == 'H:i'
assert time_picker.start == "00:00"
assert time_picker.end == "12:00"


def test_file_input(document, comm):
file_input = FileInput(accept='.txt')

Expand Down
2 changes: 2 additions & 0 deletions panel/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
DatetimeInput, DatetimePicker, DatetimeRangeInput, DatetimeRangePicker,
FileDropper, FileInput, FloatInput, IntInput, LiteralInput, NumberInput,
PasswordInput, Spinner, StaticText, Switch, TextAreaInput, TextInput,
TimePicker,
)
from .misc import FileDownload, JSONEditor, VideoStream # noqa
from .player import DiscretePlayer, Player # noqa
Expand Down Expand Up @@ -136,6 +137,7 @@
"TextEditor",
"TextInput",
"TextToSpeech",
"TimePicker",
"Toggle",
"ToggleGroup",
"ToggleIcon",
Expand Down
84 changes: 82 additions & 2 deletions panel/widgets/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import json

from base64 import b64decode
from datetime import date, datetime
from datetime import date, datetime, time as dt_time
from typing import (
TYPE_CHECKING, Any, ClassVar, Iterable, Mapping, Optional,
)
Expand All @@ -31,7 +31,7 @@
from ..layout import Column, Panel
from ..models import (
DatetimePicker as _bkDatetimePicker, TextAreaInput as _bkTextAreaInput,
TextInput as _BkTextInput,
TextInput as _BkTextInput, TimePicker as _BkTimePicker,
)
from ..util import lazy_load, param_reprs, try_datetime64_to_datetime
from .base import CompositeWidget, Widget
Expand Down Expand Up @@ -793,6 +793,86 @@ def _deserialize_value(self, value):
return value


class _TimeCommon(Widget):

hour_increment = param.Integer(default=1, doc="""
Defines the granularity of hour value increments in the UI.
""")

minute_increment = param.Integer(default=1, doc="""
Defines the granularity of minute value increments in the UI.
""")

second_increment = param.Integer(default=1, doc="""
Defines the granularity of second value increments in the UI.
""")

seconds = param.Boolean(default=False, doc="""
Allows to select seconds. By default only hours and minutes are
selectable, and AM/PM depending on the `clock` option.
""")

military_time = param.Boolean(default=True, doc="""
Whether to display time in 24 hour format.
""")

_rename: ClassVar[Mapping[str, str | None]] = {
'military_time': 'clock'
}

__abstract = True

def _process_param_change(self, msg):
msg["clock"] = "24h" if msg.pop('military_time', None) else "12h"
return super()._process_param_change(msg)


class TimePicker(_TimeCommon):
"""
The `TimePicker` allows selecting a `time` value using a text box
and a time-picking utility.

Reference: https://panel.holoviz.org/reference/widgets/TimePicker.html

:Example:

>>> TimePicker(
... value="12:59:31", start="09:00:00", end="18:00:00", name="Time"
... )
"""

value = param.ClassSelector(default=None, class_=(dt_time, str), doc="""
The current value""")

start = param.ClassSelector(default=None, class_=(dt_time, str), doc="""
Inclusive lower bound of the allowed time selection""")

end = param.ClassSelector(default=None, class_=(dt_time, str), doc="""
Inclusive upper bound of the allowed time selection""")

format = param.String(default='H:i', doc="""
Formatting specification for the display of the picked date.

+---+------------------------------------+------------+
| H | Hours (24 hours) | 00 to 23 |
| h | Hours | 1 to 12 |
| G | Hours, 2 digits with leading zeros | 1 to 12 |
| i | Minutes | 00 to 59 |
| S | Seconds, 2 digits | 00 to 59 |
| s | Seconds | 0, 1 to 59 |
| K | AM/PM | AM or PM |
+---+------------------------------------+------------+

See also https://flatpickr.js.org/formatting/#date-formatting-tokens.
""")

_rename: ClassVar[Mapping[str, str | None]] = {
'start': 'min_time', 'end': 'max_time', 'format': 'time_format'
}

_widget_type: ClassVar[type[Model]] = _BkTimePicker


class ColorPicker(Widget):
"""
The `ColorPicker` widget allows selecting a hexadecimal RGB color value
Expand Down
Loading