mirror of
https://github.com/gradio-app/gradio.git
synced 2025-04-12 12:40:29 +08:00
Timeseries improvements (#1149)
* ensure chart works with decimal values + generatesaxis lines for values below 10 * add negative example * use color palette for chart * add colors kwarg to Timeseries * fix test?
This commit is contained in:
parent
81e981eb7a
commit
eca95a549d
@ -41,9 +41,13 @@ def fn(
|
||||
}, # Label
|
||||
(audio1[0], np.flipud(audio1[1]))
|
||||
if audio1 is not None
|
||||
else os.path.join(os.path.dirname(__file__),"files/cantina.wav"), # Audio
|
||||
np.flipud(im1) if im1 is not None else os.path.join(os.path.dirname(__file__),"files/cheetah1.jpg"), # Image
|
||||
video if video is not None else os.path.join(os.path.dirname(__file__),"files/world.mp4"), # Video
|
||||
else os.path.join(os.path.dirname(__file__), "files/cantina.wav"), # Audio
|
||||
np.flipud(im1)
|
||||
if im1 is not None
|
||||
else os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg"), # Image
|
||||
video
|
||||
if video is not None
|
||||
else os.path.join(os.path.dirname(__file__), "files/world.mp4"), # Video
|
||||
[
|
||||
("The", "art"),
|
||||
("quick brown", "adj"),
|
||||
@ -73,11 +77,19 @@ def fn(
|
||||
"<button style='background-color: red'>Click Me: "
|
||||
+ radio
|
||||
+ "</button>", # HTML
|
||||
os.path.join(os.path.dirname(__file__),"files/titanic.csv"),
|
||||
os.path.join(os.path.dirname(__file__), "files/titanic.csv"),
|
||||
df1, # Dataframe
|
||||
np.random.randint(0, 10, (4, 4)), # Dataframe
|
||||
[
|
||||
im for im in [im1, im2, im3, im4, os.path.join(os.path.dirname(__file__),"files/cheetah1.jpg")] if im is not None
|
||||
im
|
||||
for im in [
|
||||
im1,
|
||||
im2,
|
||||
im3,
|
||||
im4,
|
||||
os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg"),
|
||||
]
|
||||
if im is not None
|
||||
], # Carousel
|
||||
df2, # Timeseries
|
||||
)
|
||||
@ -106,7 +118,7 @@ demo = gr.Interface(
|
||||
gr.Audio(label="Microphone", source="microphone"),
|
||||
gr.File(label="File"),
|
||||
gr.Dataframe(label="Dataframe", headers=["Name", "Age", "Gender"]),
|
||||
gr.Timeseries(x="time", y=["price", "value"]),
|
||||
gr.Timeseries(x="time", y=["price", "value"], colors=["pink", "purple"]),
|
||||
],
|
||||
outputs=[
|
||||
gr.Textbox(label="Textbox"),
|
||||
@ -137,16 +149,16 @@ demo = gr.Interface(
|
||||
["foo", "baz"],
|
||||
"baz",
|
||||
"bar",
|
||||
os.path.join(os.path.dirname(__file__),"files/cheetah1.jpg"),
|
||||
os.path.join(os.path.dirname(__file__),"files/cheetah1.jpg"),
|
||||
os.path.join(os.path.dirname(__file__),"files/cheetah1.jpg"),
|
||||
os.path.join(os.path.dirname(__file__),"files/cheetah1.jpg"),
|
||||
os.path.join(os.path.dirname(__file__),"files/world.mp4"),
|
||||
os.path.join(os.path.dirname(__file__),"files/cantina.wav"),
|
||||
os.path.join(os.path.dirname(__file__),"files/cantina.wav"),
|
||||
os.path.join(os.path.dirname(__file__),"files/titanic.csv"),
|
||||
os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg"),
|
||||
os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg"),
|
||||
os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg"),
|
||||
os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg"),
|
||||
os.path.join(os.path.dirname(__file__), "files/world.mp4"),
|
||||
os.path.join(os.path.dirname(__file__), "files/cantina.wav"),
|
||||
os.path.join(os.path.dirname(__file__), "files/cantina.wav"),
|
||||
os.path.join(os.path.dirname(__file__), "files/titanic.csv"),
|
||||
[[1, 2, 3], [3, 4, 5]],
|
||||
os.path.join(os.path.dirname(__file__),"files/time.csv"),
|
||||
os.path.join(os.path.dirname(__file__), "files/time.csv"),
|
||||
]
|
||||
]
|
||||
* 3,
|
||||
|
@ -2242,6 +2242,7 @@ class Timeseries(IOComponent):
|
||||
y: str | List[str] = None,
|
||||
label: Optional[str] = None,
|
||||
css: Optional[Dict] = None,
|
||||
colors: List[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
@ -2250,6 +2251,7 @@ class Timeseries(IOComponent):
|
||||
x (str): Column name of x (time) series. None if csv has no headers, in which case first column is x series.
|
||||
y (Union[str, List[str]]): Column name of y series, or list of column names if multiple series. None if csv has no headers, in which case every column after first is a y series.
|
||||
label (str): component name in interface.
|
||||
colors List[str]: an ordered list of colors to use for each line plot
|
||||
"""
|
||||
self.default_value = (
|
||||
pd.read_csv(default_value) if default_value is not None else None
|
||||
@ -2258,6 +2260,7 @@ class Timeseries(IOComponent):
|
||||
if isinstance(y, str):
|
||||
y = [y]
|
||||
self.y = y
|
||||
self.colors = colors
|
||||
super().__init__(label=label, css=css, **kwargs)
|
||||
|
||||
def get_template_context(self):
|
||||
@ -2265,6 +2268,7 @@ class Timeseries(IOComponent):
|
||||
"x": self.x,
|
||||
"y": self.y,
|
||||
"default_value": self.default_value,
|
||||
"colors": self.colors,
|
||||
**super().get_template_context(),
|
||||
}
|
||||
|
||||
|
@ -1080,6 +1080,7 @@ class TestTimeseries(unittest.TestCase):
|
||||
"name": "timeseries",
|
||||
"show_label": True,
|
||||
"label": "Upload Your Timeseries",
|
||||
"colors": None,
|
||||
"css": {},
|
||||
"default_value": None,
|
||||
"interactive": None,
|
||||
@ -1101,6 +1102,7 @@ class TestTimeseries(unittest.TestCase):
|
||||
"name": "timeseries",
|
||||
"show_label": True,
|
||||
"label": "Disease",
|
||||
"colors": None,
|
||||
"css": {},
|
||||
"default_value": None,
|
||||
"interactive": None,
|
||||
|
@ -35,6 +35,7 @@
|
||||
export let mode: "static" | "dynamic";
|
||||
export let label: string;
|
||||
export let show_label: boolean;
|
||||
export let colors: Array<string>;
|
||||
|
||||
export let loading_status: "complete" | "pending" | "error";
|
||||
|
||||
@ -115,7 +116,7 @@
|
||||
</script>
|
||||
|
||||
<Block
|
||||
variant={mode === "dynamic" ? "dashed" : "solid"}
|
||||
variant={mode === "dynamic" && !_value ? "dashed" : "solid"}
|
||||
color={"grey"}
|
||||
padding={false}
|
||||
>
|
||||
@ -124,7 +125,7 @@
|
||||
|
||||
{#if mode === "static"}
|
||||
{#if static_data}
|
||||
<Chart value={static_data} />
|
||||
<Chart value={static_data} {colors} />
|
||||
{:else}
|
||||
<div class="min-h-[16rem] flex justify-center items-center">
|
||||
<img src={chart_icon} alt="" class="h-10 opacity-30" />
|
||||
@ -136,6 +137,7 @@
|
||||
{y}
|
||||
{x}
|
||||
on:process={({ detail: { x, y } }) => (value = make_dict(x, y))}
|
||||
{colors}
|
||||
/>
|
||||
{:else if value === undefined}
|
||||
<div class="min-h-[8rem]">
|
||||
|
@ -8,7 +8,9 @@
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@gradio/theme": "workspace:^0.0.1",
|
||||
"@gradio/tooltip": "workspace:^0.0.1",
|
||||
"@gradio/utils": "workspace:^0.0.1",
|
||||
"d3-dsv": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.1.0"
|
||||
|
@ -1,15 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import { csvParse } from "d3-dsv";
|
||||
import { scaleLinear } from "d3-scale";
|
||||
import { line as _line, curveLinear } from "d3-shape";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
|
||||
import { get_domains, transform_values, get_color } from "./utils";
|
||||
import { colors as color_palette, ordered_colors } from "@gradio/theme";
|
||||
import { get_next_color } from "@gradio/utils";
|
||||
|
||||
import { get_domains, transform_values } from "./utils";
|
||||
|
||||
import { tooltip } from "@gradio/tooltip";
|
||||
|
||||
export let value: string | Array<Record<string, string>>;
|
||||
export let x: string | undefined = undefined;
|
||||
export let y: Array<string> | undefined = undefined;
|
||||
export let colors: Array<string> = [];
|
||||
|
||||
export let style: string = "";
|
||||
|
||||
@ -26,26 +31,40 @@
|
||||
$: scale_x = scaleLinear(x_domain, [0, 600]).nice();
|
||||
$: scale_y = scaleLinear(y_domain, [350, 0]).nice();
|
||||
$: x_ticks = scale_x.ticks(8);
|
||||
$: y_ticks = scale_y.ticks(y_domain[1] < 10 ? y_domain[1] : 8);
|
||||
$: y_ticks = scale_y.ticks(8);
|
||||
|
||||
let colors: Record<string, string>;
|
||||
$: colors = _y.reduce(
|
||||
(acc, next) => ({ ...acc, [next.name]: get_color() }),
|
||||
let color_map: Record<string, string>;
|
||||
$: color_map = _y.reduce(
|
||||
(acc, next, i) => ({ ...acc, [next.name]: get_color(i) }),
|
||||
{}
|
||||
);
|
||||
|
||||
function get_color(index: number) {
|
||||
let current_color = colors[index % colors.length];
|
||||
|
||||
if (current_color && current_color in color_palette) {
|
||||
return color_palette[current_color as keyof typeof color_palette]
|
||||
?.primary;
|
||||
} else if (!current_color) {
|
||||
return color_palette[get_next_color(index) as keyof typeof color_palette]
|
||||
.primary;
|
||||
} else {
|
||||
return current_color;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
dispatch("process", { x: _x, y: _y });
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-center align-items-center text-sm">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-center items-center text-sm">
|
||||
{#each _y as { name }}
|
||||
<div class="mx-2">
|
||||
<div class="mx-2 flex gap-1 items-center">
|
||||
<span
|
||||
class="inline-block w-[10px] h-[10px]"
|
||||
style="background-color: {colors[name]}"
|
||||
class="inline-block w-[12px] h-[12px] rounded-sm"
|
||||
style="background-color: {color_map[name]}"
|
||||
/>
|
||||
{name}
|
||||
</div>
|
||||
@ -121,7 +140,7 @@
|
||||
</g>
|
||||
|
||||
{#each _y as { name, values }}
|
||||
{@const color = colors[name]}
|
||||
{@const color = color_map[name]}
|
||||
{#each values as { x, y }}
|
||||
<circle
|
||||
r="3.5"
|
||||
@ -143,7 +162,7 @@
|
||||
{/each}
|
||||
|
||||
{#each _y as { name, values }}
|
||||
{@const color = colors[name]}
|
||||
{@const color = color_map[name]}
|
||||
{#each values as { x, y }}
|
||||
<circle
|
||||
use:tooltip={{ color, text: `(${x}, ${y})` }}
|
||||
|
@ -60,11 +60,11 @@ export function transform_values(
|
||||
for (let j = 0; j < _a.length; j++) {
|
||||
let [name, x] = _a[j];
|
||||
if (name === transformed_values.x.name) {
|
||||
transformed_values.x.values.push(parseInt(x, 10));
|
||||
transformed_values.x.values.push(parseFloat(x));
|
||||
} else {
|
||||
transformed_values.y[j - 1].values.push({
|
||||
y: parseInt(_a[j][1], 10),
|
||||
x: parseInt(_a[0][1], 10)
|
||||
y: parseFloat(_a[j][1]),
|
||||
x: parseFloat(_a[0][1])
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -72,21 +72,3 @@ export function transform_values(
|
||||
|
||||
return transformed_values;
|
||||
}
|
||||
|
||||
let c = 0;
|
||||
export function get_color(): string {
|
||||
let default_colors = [
|
||||
[255, 99, 132],
|
||||
[54, 162, 235],
|
||||
[240, 176, 26],
|
||||
[153, 102, 255],
|
||||
[75, 192, 192],
|
||||
[255, 159, 64]
|
||||
];
|
||||
|
||||
if (c >= default_colors.length) c = 0;
|
||||
|
||||
const [r, g, b] = default_colors[c++];
|
||||
|
||||
return `rgb(${r},${g},${b})`;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@gradio/theme": "workspace:^0.0.1"
|
||||
"@gradio/theme": "workspace:^0.0.1",
|
||||
"@gradio/utils": "workspace:^0.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
const browser = typeof document !== "undefined";
|
||||
import { colors } from "@gradio/theme";
|
||||
import { getNextColor } from "./utils";
|
||||
import { colors, ordered_colors } from "@gradio/theme";
|
||||
import { get_next_color } from "@gradio/utils";
|
||||
|
||||
export let value: Array<[string, string | number]> = [];
|
||||
export let show_legend: boolean = false;
|
||||
@ -38,7 +38,7 @@
|
||||
if (typeof label === "string") {
|
||||
mode = "categories";
|
||||
if (!(label in color_map)) {
|
||||
let color = getNextColor(Object.keys(color_map).length);
|
||||
let color = get_next_color(Object.keys(color_map).length);
|
||||
color_map[label] = color;
|
||||
}
|
||||
} else {
|
||||
@ -51,7 +51,7 @@
|
||||
for (const col in color_map) {
|
||||
const _c = color_map[col].trim();
|
||||
if (_c in colors) {
|
||||
_color_map[col] = colors[_c];
|
||||
_color_map[col] = colors[_c as keyof typeof colors];
|
||||
} else {
|
||||
_color_map[col] = {
|
||||
primary: browser ? name_to_rgba(color_map[col], 1) : color_map[col],
|
||||
|
@ -1,9 +1,3 @@
|
||||
import { ordered_colors } from "@gradio/theme";
|
||||
|
||||
export function randInt(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
export const getNextColor = (index: number): string => {
|
||||
return ordered_colors[index % ordered_colors.length];
|
||||
};
|
||||
|
@ -9,7 +9,7 @@ export const ordered_colors = [
|
||||
"cyan",
|
||||
"lime",
|
||||
"pink"
|
||||
];
|
||||
] as const;
|
||||
|
||||
// https://play.tailwindcss.com/ZubQYya0aN
|
||||
export const colors = {
|
||||
@ -23,4 +23,4 @@ export const colors = {
|
||||
cyan: { primary: "#0891b2", secondary: "#7dd3fc" },
|
||||
lime: { primary: "#84cc16", secondary: "#d9f99d" },
|
||||
pink: { primary: "#db2777", secondary: "#f9a8d4" }
|
||||
};
|
||||
} as const;
|
||||
|
@ -9,12 +9,15 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="bg-black bg-opacity-80 text-white border-r-2 p-1 absolute text-xs flex items-center justify-center"
|
||||
class="bg-black bg-opacity-80 text-white py-1 px-[0.4rem] absolute text-xs flex items-center justify-center rounded"
|
||||
bind:offsetWidth={w}
|
||||
bind:offsetHeight={h}
|
||||
style="
|
||||
top: {y - h / 2}px;
|
||||
left: {x - w - 7}px;"
|
||||
>
|
||||
<span class="inline-block w-3 h-3 mr-1" style="background: {color}" />{text}
|
||||
<span
|
||||
class="inline-block w-3 h-3 mr-1 rounded-sm"
|
||||
style="background: {color}"
|
||||
/>{text}
|
||||
</div>
|
||||
|
1
ui/packages/utils/README.md
Normal file
1
ui/packages/utils/README.md
Normal file
@ -0,0 +1 @@
|
||||
# `@gradio/table`
|
13
ui/packages/utils/package.json
Normal file
13
ui/packages/utils/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@gradio/utils",
|
||||
"version": "0.0.1",
|
||||
"description": "Gradio UI packages",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@gradio/theme": "workspace:^0.0.1"
|
||||
}
|
||||
}
|
5
ui/packages/utils/src/color.ts
Normal file
5
ui/packages/utils/src/color.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { colors, ordered_colors } from "@gradio/theme";
|
||||
|
||||
export const get_next_color = (index: number): keyof typeof colors => {
|
||||
return ordered_colors[index % ordered_colors.length];
|
||||
};
|
1
ui/packages/utils/src/index.ts
Normal file
1
ui/packages/utils/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./color";
|
@ -9,6 +9,39 @@
|
||||
5,15,20
|
||||
6,21,24
|
||||
7,28,28`;
|
||||
|
||||
const csv2 = `time,value,price
|
||||
0.01,0.01,0.04
|
||||
0.02,0.03,0.08
|
||||
0.03,0.06,0.12
|
||||
0.04,0.10,0.16
|
||||
0.05,0.15,0.20
|
||||
0.06,0.21,0.24
|
||||
0.07,0.28,0.28`;
|
||||
|
||||
const csv3 = `time,value,price
|
||||
1.1,1.1,4.4
|
||||
2.2,3.3,8.8
|
||||
3.3,6.6,12.12
|
||||
4.4,10.10,16.16
|
||||
5.5,15.15,20.20
|
||||
6.6,21.21,24.24
|
||||
7.7,28.28,28.28`;
|
||||
|
||||
const csv4 = `time,value,price
|
||||
1,-1.1,-4.4
|
||||
2,-3.3,-8.8
|
||||
3,-6.6,-12.12
|
||||
4,-10.10,-16.16
|
||||
5,-15.15,-20.20
|
||||
6,-21.21,-24.24
|
||||
7,-28.28,-28.28`;
|
||||
</script>
|
||||
|
||||
<Chart value={csv} />
|
||||
|
||||
<Chart value={csv2} colors={["teal", "pink"]} />
|
||||
|
||||
<Chart value={csv3} colors={["purple", "lime"]} />
|
||||
|
||||
<Chart value={csv4} />
|
||||
|
12
ui/pnpm-lock.yaml
generated
12
ui/pnpm-lock.yaml
generated
@ -136,7 +136,9 @@ importers:
|
||||
|
||||
packages/chart:
|
||||
specifiers:
|
||||
'@gradio/theme': workspace:^0.0.1
|
||||
'@gradio/tooltip': workspace:^0.0.1
|
||||
'@gradio/utils': workspace:^0.0.1
|
||||
'@types/d3-dsv': ^3.0.0
|
||||
'@types/d3-scale': ^4.0.2
|
||||
'@types/d3-shape': ^3.0.2
|
||||
@ -144,7 +146,9 @@ importers:
|
||||
d3-scale: ^4.0.2
|
||||
d3-shape: ^3.1.0
|
||||
dependencies:
|
||||
'@gradio/theme': link:../theme
|
||||
'@gradio/tooltip': link:../tooltip
|
||||
'@gradio/utils': link:../utils
|
||||
d3-dsv: 3.0.1
|
||||
d3-scale: 4.0.2
|
||||
d3-shape: 3.1.0
|
||||
@ -176,8 +180,10 @@ importers:
|
||||
packages/highlighted-text:
|
||||
specifiers:
|
||||
'@gradio/theme': workspace:^0.0.1
|
||||
'@gradio/utils': workspace:^0.0.1
|
||||
dependencies:
|
||||
'@gradio/theme': link:../theme
|
||||
'@gradio/utils': link:../utils
|
||||
|
||||
packages/html:
|
||||
specifiers: {}
|
||||
@ -246,6 +252,12 @@ importers:
|
||||
dependencies:
|
||||
'@gradio/atoms': link:../atoms
|
||||
|
||||
packages/utils:
|
||||
specifiers:
|
||||
'@gradio/theme': workspace:^0.0.1
|
||||
dependencies:
|
||||
'@gradio/theme': link:../theme
|
||||
|
||||
packages/video:
|
||||
specifiers:
|
||||
'@gradio/atoms': workspace:^0.0.1
|
||||
|
Loading…
x
Reference in New Issue
Block a user