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:
pngwn 2022-05-03 22:05:17 +01:00 committed by GitHub
parent 81e981eb7a
commit eca95a549d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 152 additions and 66 deletions

View File

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

View File

@ -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(),
}

View File

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

View File

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

View File

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

View File

@ -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})` }}

View File

@ -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})`;
}

View File

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

View File

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

View File

@ -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];
};

View File

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

View File

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

View File

@ -0,0 +1 @@
# `@gradio/table`

View 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"
}
}

View 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];
};

View File

@ -0,0 +1 @@
export * from "./color";

View File

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

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