Compare commits

...

83 Commits

Author SHA1 Message Date
Claude Code
1ebad418a0 test(partition): cover transformProps' v1-record path and level mapping
transformData.ts had thorough tests, but transformProps.ts itself --
the glue that decides between the v1 flat-record path and the legacy
nested-hierarchy passthrough, maps groupby columns through verboseMap,
and parses partitionLimit/Threshold -- had none.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 01:25:57 -07:00
Claude Code
833fca4e1f fix(migrations): don't KeyError on a query_context missing "queries"
upgrade_slice assumed a stored query_context always carries a
"queries" key. An atypical one (e.g. hand-edited via the API) missing
it raised a bare KeyError inside the broad except, after viz_type had
already been flipped -- leaving the slice half-migrated (new
viz_type, but stale params/query_context in the old shape).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 01:23:07 -07:00
Claude Code
b0df21aca3 test(rose): cover click-to-drill and the color-priming order invariant
EchartsRose.tsx had no component-level test at all: the click-to-drill
dataIndex mapping and, more importantly, the CategoricalColorNamespace
priming-order dependency (the scale must be primed in seriesNames order
before building the drilled pie's slices, since getScale() returns a
fresh scale each time and slices are sorted by value, a different order
than seriesNames) were both unverified -- easy to silently break into
wrong colors with no error.

Verified the color-priming test actually catches a regression by
temporarily reordering the priming call to happen after the value-sort
(reproducing the exact bug class described above): the test failed as
expected, then passed again once reverted. Getting there required also
resetting @superset-ui/core's process-wide label-color singleton between
tests -- it remembers a label's color per sliceId, so without the reset
whichever test ran first would permanently decide the colors for every
later test in the file, making a naive version of this test tautological
(always passing regardless of priming order).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 01:13:23 -07:00
Claude Code
a208366b4f refactor(charts): extract a shared sort-metric/orderby helper for 3 migrated buildQuery.ts
partition, paired-t-test, and parallel-coordinates each independently
reimplemented the same ~15 lines of legacy-parity logic: resolve a sort
metric from timeseries_limit_metric, append it to the selected metrics if
missing, and build the orderby tuple. The three differ only in two real,
legacy-behavior-derived ways (verified against master:superset/viz.py
before extracting): whether an unset sort metric falls back to the first
selected metric (partition: yes; the other two: no), and whether ordering
only happens when order_desc is explicitly set (paired-t-test/
parallel-coordinates: yes; partition always orders, just flips direction).

Extracted `buildSortMetricOrderby()` into
@superset-ui/chart-controls/utils, parameterized on those two axes so the
three charts keep their real, intentional differences rather than being
forced into false uniformity. horizon was left alone -- its ordering
logic is simpler (orders directly by the first metric, no
timeseries_limit_metric override or metrics-list injection) and isn't
actually the same shape as the other three.

Also fills a test gap the extraction surfaced: paired-t-test's buildQuery
tests only exercised order_desc: true with a sort metric set, never the
"append to metrics but don't order" case that is the entire point of its
order-gating behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 01:03:48 -07:00
Claude Code
82f05043f9 fix(time-pivot): extract Bullet's legend-row estimator, fix a shared overlap bug
Bullet's dynamic legend-row estimation (added to fix a legend-overlapping-
plot bug) was local to its own transformProps.ts. TimePivot has the exact
same class of bug: with period_limit unset a chart can have 50+ series
(current + many priors), and its grid.top was a fixed constant regardless
of legend size, so a wide legend can wrap onto extra rows with nothing
reserving space for them.

Extracted the estimator into utils/legendLayout.ts as
estimateWrappedLegendRowCount() (also naming its previously-inline magic
numbers), reused it from Bullet, and wired it into TimePivot's grid.top so
extra wrapped rows get extra height instead of overlapping the plot.
TimePivot's single-row case is unchanged (same fixed padding as before).

Added tests for the shared helper itself, and for TimePivot: a large
legend reserves more grid.top than a small one, and a hidden legend
reserves none of the extra space.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 00:55:31 -07:00
Claude Code
8bd085613f fix(migrations): scope downgrade to the migration that actually upgraded a slice
Two related bugs found in a post-merge self-review, both stemming from
multiple MigrateViz subclasses sharing one target_viz_type (e.g. both
MigrateLineChart and MigrateCompareChart migrate onto
echarts_timeseries_line):

- MigrateViz.downgrade()'s SQL filter (target_viz_type + a form_data_bak
  marker) is coarse by necessity and matches slices from ANY subclass
  sharing that target. Running e.g. `compare`'s downgrade would therefore
  also revert already-settled `line`-sourced slices back to the legacy
  `line` viz_type -- a plugin this same PR deletes. Each row's own
  form_data_bak was still correct (no data corruption), but the migration
  wasn't independently revertible the way its name implies. Fixed by
  checking form_data_bak["viz_type"] == cls.source_viz_type in
  downgrade_slice() before touching a row -- the backup already records
  which migration produced it, no new field needed.

- The `superset viz-migrations downgrade --id` CLI path had the same root
  cause one level up: PREVIOUS_VERSION was keyed by target_viz_type, so
  building it from MIGRATIONS silently let the last-registered subclass
  for a shared target win the dict, and `migrate_by_id` would invoke the
  wrong class's downgrade_slice for any chart from the losing subclass.
  With the fix above, that would have started silently no-op'ing instead
  of misfiring, which just relocated the bug. Removed PREVIOUS_VERSION
  entirely and look the migration up by the slice's own backed-up source
  viz_type via MIGRATIONS (already correctly keyed 1:1 by source type).

Added a regression test exercising the exact MigrateLineChart /
MigrateCompareChart collision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 00:44:06 -07:00
Claude Code
3d7a7d43b0 fix(timeseries): remove percent-change baseline's getOption() race, add its first test coverage
The draggable percent-change baseline had zero test coverage despite six
separate production fixes landing against it (crash from a non-group
graphic element, hardcoded colors, NaN from category-axis coercion, a
baseline that reset on every rerender, unthrottled per-pointer-move
setOption calls, and a chart.getOption() undefined crash on warm
navigation). That last one was patched as a symptom rather than a cause:
chart.getOption() reflects the live ECharts instance's internal state,
which can still be empty for a tick after mount.

Root-cause fix: read series data from the echartOptions prop instead --
it's already the source of truth this effect depends on, available
synchronously, and removes the mount race entirely rather than working
around it.

Also named the drag handle's pixel-geometry magic numbers
(BASELINE_HANDLE_WIDTH etc.) and added the first test suite for the
interaction itself: mount position, drag-to-rebase math, snap-to-nearest,
a no-op when dragging back onto the active baseline, the empty-series
edge case, ondragend redraw, and graphic cleanup on unmount.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 00:23:53 -07:00
Claude Code
1b853d6343 fix(deck-multi): guard against stale layer responses, remove dead legacy branch
Two issues found in a post-merge self-review of the deck_multi rearchitecture:

- loadLayers() had no guard against a layer fetch that resolves after
  deck_slices (or the visibility filter) has already changed again. A stale
  response could still write into subSlicesLayers/layerErrors and pollute
  the autozoom feature accumulator for the new, already-reset generation.
  Fixed with a generation counter: each loadLayers call bumps it, each
  in-flight fetch captures the generation it started under, and its
  callbacks bail out if that generation is no longer current.

- deck_multi's buildQuery always returns an empty queries array (it issues
  no query of its own -- every layer self-fetches), so `payload` is always
  undefined in production. The `payload?.data?.slices`/`payload?.data?.features`
  reads in Multi.tsx were therefore dead code, reachable only because the
  Jest fixtures synthesized a payload shape the real transformProps can
  never produce. Removed the dead branches, made `payload` optional in
  DeckMultiProps to match reality, and converted the fixtures in
  Multi.test.tsx/Multi.color.test.tsx to mock SupersetClient.get/post and
  drive the real v1 fetch path instead.

Added two new tests: one confirming a stale, superseded layer response is
ignored rather than corrupting the current render, and one confirming
autozoom correctly accumulates points across layers that resolve out of
order (the scenario the original v1-autozoom fix was written for, but
never had a multi-layer test).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 23:59:05 -07:00
Claude Code
464674f193 fix(migrations): re-point bubble-chart migration onto master's current alembic head
master added version_transaction_issued_at_index (d3b9a1f6c204) on top of
add_extension_storage_table (e5f6a7b8c9d0), which our bubble-chart migration
was still pointing at as its down_revision -- producing two heads and
failing "superset db upgrade" (and everything downstream: load_examples,
postgres/mysql/sqlite integration tests, cypress, playwright) with
"Multiple head revisions are present". Re-point onto the new head so the
chain is linear again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 20:35:30 -07:00
Claude Code
c7a83794d2 Merge remote-tracking branch 'origin/master' into remove-legacy-viz-pipeline 2026-07-27 20:22:58 -07:00
Evan Rusackas
dbc561f80f chore(viz)!: rebuild Nightingale Rose on ECharts, removing nvd3 entirely (#42381)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 20:19:17 -07:00
rusackas
86003221bc test: remove test_viz_query_obj.py leftover import of deleted viz module
This test module imported `superset.viz`, which this PR removes, breaking
unit-test collection. The jinja-preservation regression it guarded is
already covered by
core_tests.py::test_split_adhoc_filters_preserves_jinja_templates against
the new (non-viz.py) code path, so the file is redundant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 19:04:14 -07:00
rusackas
9615e7c024 fix(viz): drop dead legacy-API dataset-required banner left over from rebase
The explore_json/viz.py removal commit deleted this banner's supporting
state and imports (it only applied to useLegacyApi chart types), but a
rebase conflict against master reintroduced the JSX referencing the
now-undefined vizTypeNeedsDataset/setShowDatasetModal/Alert. Also picks
up a prettier formatting fixup in StatefulChart.tsx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:41:43 -07:00
rusackas
80c8d9f388 fix(plugins): drop stale "Legacy" wording from renamed chart package descriptions
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:24:00 -07:00
Claude Code
59f1d91b07 chore(explore): drop comments pointing at the long-gone nvd3_vis.css
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:59 -07:00
Claude Code
a4fc9ae493 chore(viz)!: delete the unregistered preset-chart-nvd3 package
Nothing has registered this package since the Bullet and Time-series
Period Pivot charts moved to ECharts; remove the workspace, its
tsconfig reference, the custom-rules grandfather entry, and its
lockfile entries (which also sweeps the nvd3-only fast-safe-stringify
dependency). The nvd3-fork library itself remains a dependency of
plugin-chart-rose, whose legend and tooltip still use it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:58 -07:00
Claude Code
5bbe3ac750 fix(rose): use the theme text color for chart and legend text
The nvd3-fork legend and axis labels render SVG text with the default
black fill, which is unreadable against a dark theme background.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:58 -07:00
Claude Code
1aeff038d7 test(timeseries): cover the percent-change flag end-to-end from migrated params
Adds transformProps-level coverage for rebase_percent_change: series
rebased from the first point with a forced percent axis format, and the
snake_case key the compare-chart migration stores read through real
ChartProps camelization. Also guards the rebase against form data with
no explicit x-axis by falling back to the temporal alias, matching
extractSeries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:57 -07:00
Claude Code
b73f6a8bd5 fix(bullet): range labels inside their bands, always-on tooltip, legend-aware grid
Range labels move from mid-bar scatter points onto the markArea bands
(insideTopRight), so each label sits inside the range it names and the
rightmost label no longer clips at the chart edge. The tooltip stays
enabled regardless of the Show labels/legend toggles and the measure bar
tooltip names the range the value falls within. The grid top now
estimates wrapped legend rows from item text widths instead of assuming
a single row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:57 -07:00
Claude Code
1859b375a7 chore(migrations): re-id compare-chart migration and re-point bubble migration after rebase
Master's add_extension_storage_table migration landed with revision id
e5f6a7b8c9d0, colliding with the compare-chart migration's id; re-id it
to 88360afb61ed and chain the bubble migration off master's head.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:56 -07:00
Claude Code
b85468ee17 chore: drop unused feature_flag_manager import after rebase
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:55 -07:00
Claude Code
2261c61791 docs(embedded): document the iframe session cookie race in the pivot spec
Also retriggers CI so the merge ref picks up the master fix from #42274
(an empty commit does not trigger the path-gated workflows).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:55 -07:00
Claude Code
9fe31e3ec5 test(embedded): store a query_context on the pivot collapse fixture chart
The spec's fixture chart was created with params only. Real charts saved
through Explore always persist a query_context, and guest (embedded)
chart data requests are validated against the stored chart: with no
stored query_context and no columns/groupby params keys, the pivot
payload's query columns read as tampering and every request 403s.

The suite previously passed only when the embedded iframe happened to
ride the admin session from storageState instead of the guest token (a
cookie race between the /embedded response's anonymous session and the
SDK's csrf/guest-token fetches), which masked the 403 on master.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:54 -07:00
Claude Code
c7f0167a28 test(charts): update warm-up cache assertions for the actionable message
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:54 -07:00
Claude Code
52b5110a53 style: prettier formatting for UPDATING.md and SavedQueries test
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:53 -07:00
Claude Code
a33ab097e1 fix(time-table): escape backslashes in flattened labels; actionable null-query-context errors
Escaping only commas let group tuples like (a\\, b) and (a-comma, b)
flatten to identical column keys; backslashes are now escaped first, with
a regression test pinning the collision pair. The cache warm-up error for
charts whose query context has not been generated yet tells the operator
how to resolve it, and UPDATING.md documents the one-time lazy generation
for charts migrated in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:53 -07:00
Evan Rusackas
05ce4d41bf feat(bullet): rebuild the Bullet Chart on ECharts (#42225)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 15:23:53 -07:00
Evan Rusackas
8cd636bcd0 feat(time-pivot): rebuild Time-series Period Pivot on ECharts (#42245)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 15:23:53 -07:00
Evan Rusackas
18231d9bd4 feat(timeseries): percent-change rebasing with a draggable baseline (#42247)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 15:23:52 -07:00
Evan
ca17830175 fix: cast Arc test color accessors through unknown for tsc
lint-frontend's tsc build failed with TS2352 on the direct Accessor ->
ColorAccessor cast; go through unknown first as the compiler suggests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:52 -07:00
Evan
2be95c6283 fix: repair type errors from removed useLegacyApi metadata field
Three plugin tests still asserted on ChartMetadata.useLegacyApi, which
this PR drops entirely, and the Arc layer test called deck.gl color
accessors without narrowing their Accessor union type. Both broke
lint-frontend's tsc check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:51 -07:00
Claude Code
6edeafff6b fix(deck-arc): default missing layer colors instead of crashing
The Arc layer read color_picker/target_color_picker straight off form data
and dereferenced .r/.g/.b, so a saved chart without those controls crashed
the ArcLayer on init ("Cannot read properties of undefined (reading 'r')").
This surfaced in deck.gl Multiple Layers, where sub-slices arrive as raw
saved form data without control-default hydration and the Arc example never
persisted target_color_picker. Fall back to the control-panel default color
(PRIMARY_COLOR) when a picker is absent, matching how Path and Scatter
already guard, and harden Polygon's fill/stroke pickers the same way. Adds
Arc color-accessor tests for the missing and present cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:51 -07:00
Claude Code
b2d2aa4452 feat(deck-multi): surface layer load failures in the chart
When a deck.gl Multiple Layers sub-layer fails to load (e.g. it is bound to
a dataset that lacks the columns it needs), the error previously only threw
to the browser console, leaving the map looking silently empty. Capture the
per-slice failure and render a warning Alert over the map, normalized via
getClientErrorObject so the server message (e.g. "Columns missing in
dataset") is shown. Errors reset on each reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:51 -07:00
Claude Code
09a034c573 test(plugins): cover the migrated calendar/chord/country-map registration
Codecov flagged the calendar transformProps and the calendar/chord/
country-map plugin index modules as uncovered. Add plugin-registration
tests (instantiate the plugin, assert v1 metadata and the buildQuery
loader), a calendar transformProps test (v1 reshape plus display-option
passthrough, and the non-array passthrough branch), and a calendar
buildQuery case for an unrecognized subdomain granularity falling back to
minutes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:51 -07:00
Claude Code
f2fd536823 fix(deck-multi): query layers against their real datasource, not stale params
Each deck.gl Multiple Layers sub-layer is an independent saved chart with
its own dataset, and its layer query must hit that dataset. fetchSubslices
rebuilt the layer form_data from the saved params, whose `datasource`
string can be stale -- example charts hardcode a datasource id that does
not match the imported dataset's real id. The layer query then went to the
wrong dataset and the server rejected it with 400 "Columns missing in
dataset" (e.g. LON/LAT), so no layer rendered. Standalone the chart works
because it resolves its datasource from the slice relationship, not params.

Use the chart's authoritative datasource_id/datasource_type from the chart
API to build the layer datasource, falling back to params only when absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:51 -07:00
Claude Code
9c6b1f0eb6 fix(deck-multi): correct the points-collection typing to unblock tsc
The GET_POINTS_BY_VIZ_TYPE map annotated its values as
(features: JsonObject[]) => [number, number][], but the per-layer
getPoints helpers have looser, inconsistent signatures (getPointsGeojson
takes Point[]), so the map failed tsc. Replace it with an explicit-spread
collectPoints(features) mirroring the original getAdjustedViewport, which
type-checks and works for both the payload and the per-layer accumulator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:51 -07:00
Claude Code
f2055d8446 fix(deck-multi): autozoom to layer data in the v1 fetch path
deck.gl Multiple Layers computed its viewport once, from
props.payload.data.features, and never again. In the v1 path that payload
is empty (each layer is fetched client-side after mount), so autozoom had
no points to fit and the camera stayed at the default location while the
layers rendered off-screen -- the map showed but appeared to have no data.

Accumulate each layer's features as it is fetched and refit the viewport
to the combined points (when autozoom is enabled), mirroring what the
legacy pre-merged payload allowed. Also factor the per-viz_type point
collection into a shared helper. Adds a v1-path test covering both layer
rendering and the viewport refit, which the existing suite (all legacy
payload.data.slices) did not exercise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:50 -07:00
Claude Code
fbd088af48 chore(paired-t-test): drop the unused prop-types dependency
Nothing in the plugin imports prop-types since the class components were
migrated to function components. Remove it and its lockfile reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:50 -07:00
Claude Code
d7c34215a2 fix(paired-t-test): bound the scroll container to the chart height
The container relied on height: 100%, which does not resolve because the
chart's parent is not height-bounded in the v1 render path, so tall output
(many groups across multiple metrics) still clipped without scrolling.
Thread the explicit height SuperChart passes via chartProps through
transformProps and apply it to the scroll container, matching the pattern
used by the pivot-table chart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:50 -07:00
Claude Code
a82128cde0 refactor(paired-t-test): use the antd Table and drop the reactable dependency
Replace the interim plain-table rendering with the design-system antd Table
from @superset-ui/core/components: native column sorting via the ported
comparators, per-cell lift/p-value/significance coloring through semantic
theme tokens (no bespoke CSS classes), and row-click control selection via
onRow. Removes the reactable dependency entirely (its fragile, React-18-
incompatible rendering was the production bug) and the leftover reactable-*
styling. Also drops the now-stale distributions/cephes lockfile entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:50 -07:00
Claude Code
9ff15cb53e fix(paired-t-test): scroll the chart when tables overflow its height
The container styled the non-existent .scrollbar-container class, so the
chart never scrolled and tall output (many groups across multiple metrics)
was clipped with no way to reach the rest. Make the chart root fill its
allotted height and scroll on overflow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:49 -07:00
Claude Code
ac7f53326f fix(paired-t-test): render the table without the reactable dependency
The migrated chart returned correct data (verified end-to-end) yet showed
table headers with no body rows in production builds, while every jest
path rendered rows. The common factor was reactable, an unmaintained 2016
library that relies on React-18-incompatible legacy lifecycles
(componentWillMount/componentWillReceiveProps) and identifies its row
children by stringifying components -- fragile in a production bundle.

Replace it with a plain semantic table that reproduces every behavior
(control-row selection, per-cell lift/p-value/significance coloring, and
the same column sort comparators), reusing the existing CSS class hooks so
the surrounding styles are unchanged. Rendering is now deterministic and
fully unit-testable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:49 -07:00
Evan
feb224686b fix(tests): drop removed useLegacyApi prop from ChartRenderer test metadata
The legacy-pipeline removal deleted useLegacyApi from ChartMetadataConfig;
the enableNoResults test still passed it, breaking tsc in lint-frontend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:49 -07:00
Claude Code
f93cc1a67b test(paired-t-test): prove the v1 pipeline renders table rows end-to-end
Guards the "headers render but no body rows" failure mode by driving the
real transformProps -> PairedTTest pipeline with a realistic flat
/api/v1/chart/data timeseries response (main.birth_names shape): asserts
transformProps keys the reshaped data by metric label with one series per
group tuple, and that the rendered tables contain a body row per group,
including when optional precision/significance controls are absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:49 -07:00
Claude Code
d2911672bf fix(explore): honor chart enableNoResults metadata in ChartRenderer
ChartRenderer computed the SuperChart enableNoResults prop purely from
server-pagination + AG Grid filter state (bypassNoResult) and never read
the chart plugin's own enableNoResults metadata. Charts that fetch their
own data and issue no top-level query, like deck.gl Multiple Layers, set
enableNoResults: false but still hit the "No results were returned for
this query" empty state in explore, which pre-empts their self-fetching
renderer from ever mounting.

AND the chart's enableNoResults metadata (defaulting to true) into
bypassNoResult so deck_multi and similar self-fetching charts render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:49 -07:00
Evan Rusackas
f9dfea8791 test(plugins): regression guards from the migration bug sweep
Adds low-risk import-and-assert tests across migrated plugins, guarding the
classes of bugs found while testing this branch:
- is_timeseries charts must expose a datetime control (calendar, horizon, rose,
  partition). Partition genuinely lacked one, so add sections.legacyTimeseriesTime
  to its control panel (same 500 as paired-t-test otherwise).
- deck.gl Multiple Layers must issue an empty query (self-fetches its layers).
- deck.gl Arc is timeseries only when time_grain_sqla is set.
- Time Pivot's numeric x-axis format must render a number, not the literal
  SMART_NUMBER string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:48 -07:00
Evan Rusackas
a5aa619668 test(paired-t-test): mock the statistics module instead of removed distributions
Removing the distributions dependency left TTestTable.test.tsx mocking a module
the component no longer imports, so the p-value/significance tests failed. Mock
./statistics.studentTwoSidedPValue to a deterministic 0.02 instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:48 -07:00
Evan Rusackas
dfc745c922 fix(nvd3,controls): time-pivot x-axis number format + single-line async multiselect
- Time Pivot's x-axis is a numeric offset within the period (its x_axis_format
  defaults to SMART_NUMBER), but NVD3Vis ran it through getTimeFormatter, which
  made d3 print the literal string "SMART_NUMBER" on every tick. Format it as a
  number instead. (Pre-existing bug, bycatch.)
- SelectAsyncControl never forwarded the Select 'oneLine' prop, so many selected
  tags wrapped outside the control's fixed-height box (visible on deck.gl
  Multiple Layers). Pass oneLine for multi mode so tags collapse to a '+N' tag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:48 -07:00
Evan Rusackas
936ec951d5 fix(paired-t-test): compute p-values without the Node-only distributions dep
The chart imported 'distributions' to compute the Student's t p-value, but that
package references Node's Buffer global, which is only polyfilled in dev builds
-- so the chart rendered in dev and threw "ReferenceError: Buffer is not
defined" in production. Replace it with a self-contained, browser-safe
two-sided p-value via the regularized incomplete beta function, verified
against standard t-table critical values, and drop the dependency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:48 -07:00
Evan Rusackas
acc93dfe33 fix(deck-multi): render the map instead of the no-results empty state
deck.gl Multiple Layers issues no query of its own -- each layer is a saved
chart that fetches its data client-side, so buildQuery is intentionally empty.
Dropping useLegacyApi exposed the default enableNoResults=true, so the empty
query response now shows "No results were returned for this query" and the map
never renders. Set enableNoResults: false, matching the other self-fetching
charts (Select filter, DeckglLayerVisibility).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:47 -07:00
Evan Rusackas
a973290890 fix(paired-t-test): restore the datetime column control
The paired t-test chart's buildQuery sends is_timeseries, so the backend
requires a datetime column, but its migrated control panel has no time
section. Creating the chart fails with "Datetime column not provided as part
table configuration and is required by this type of chart" with no way to
supply one. Add the shared legacyTimeseriesTime section, matching its sibling
timeseries charts (rose, horizon), so a granularity column can be selected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:23:47 -07:00
Evan
377e8e6345 fix: rebase bubble-chart migration onto master's current alembic head
master added strip_metricsqlexpressions_from_ag_grid_params (d24e6b0a9c7f)
on top of shadow_live_row_indexes (8f3a1b2c4d5e) after this branch's
bubble-chart migration was last pointed at 8f3a1b2c4d5e, producing two
alembic heads and failing `db upgrade` across every integration/E2E job.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:47 -07:00
Evan
71443460d1 fix: resolve oxlint unused-var/param errors and remove orphaned legacy test
Drops the dead `method` param from getChartDataRequest (the legacy
GET/POST branching it supported no longer exists now that chart data
always POSTs to /api/v1/chart/data), removes bubble-only NVD3Vis props
(entity/maxBubbleSize/xField/yField/sizeField) and the unused module-level
`formatter` left over from the bubble->bubble_v2 migration, and deletes
plugins/legacy-preset-chart-nvd3/test/TimePivot/controlPanel.test.ts, an
orphaned test for a src/ directory that no longer exists after the
rename to preset-chart-nvd3 (superseded by
plugins/preset-chart-nvd3/test/TimePivot.test.ts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:47 -07:00
Evan
dcda71e869 fix: point bubble-chart migration at master's actual alembic head
down_revision on d4e5f6a7b8c9 pointed at b1c2d3e4f5a6
(add_subjects_tables), which already has a child on master
(56cd24c07170 -> 8f3a1b2c4d5e). That left two alembic heads and
broke `superset db upgrade` in every DB-backed CI job. Chain the
migration after 8f3a1b2c4d5e, master's real current head, instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:46 -07:00
Evan
d59120a632 fix: resolve alembic multiple-heads conflict in migration chain
down_revision on d4e5f6a7b8c9 still pointed at 3a8e6f2c1b95, which
master's add_subjects_tables migration (b1c2d3e4f5a6) also forked
from, leaving two alembic heads and breaking `superset db upgrade`
in every DB-backed CI job.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:46 -07:00
Evan
ba795ccfa0 fix: format Multi.tsx and update Multi.color.test.tsx for registry-based layer loading
Multi.tsx now resolves sublayer buildQuery/transformProps via the chart
plugin registries instead of the removed legacy explore.ts helper; update
the color test's mocks to register deck_scatter/deck_arc stubs and mock
SupersetClient.post (matching the new fetch call) instead of .get.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:45 -07:00
Evan
6a5049daed fix: repair stale package-lock.json and alembic migration branch
- Remove dangling package-lock.json link entries for the removed
  legacy-* plugin workspaces and regenerate entries for their
  renamed plugin-chart-* counterparts; npm ci was failing with
  EUSAGE because the lockfile referenced workspace paths that no
  longer exist on disk.
- Re-parent the bubble-chart-to-echarts migration
  (d4e5f6a7b8c9) onto the current single migration head
  (3a8e6f2c1b95) instead of a now-superseded ancestor, resolving
  the "Multiple head revisions" alembic error that was failing
  test-sqlite/test-postgres/test-mysql/cypress/playwright.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:45 -07:00
Evan
83e7098ee8 fix: restore OAuth2RedirectError import after rebase
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:23:44 -07:00
Claude Code
b71ea77080 docs: add UPDATING.md entries for the legacy viz pipeline removal and drop the working trackers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:23:43 -07:00
Evan Rusackas
b3435a04cf chore(viz)!: remove explore_json endpoints, viz.py and the legacy chart data pipeline (#41750)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 15:23:41 -07:00
Evan Rusackas
1771f05d33 chore(viz): drop the legacy- prefix from migrated chart plugin packages (#41751)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 15:05:59 -07:00
Claude Code
3dfe193ca0 chore(viz): update SCOPE tracker — all charts migrated, phases 2-3 in review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:54:57 -07:00
Evan Rusackas
c54abcfda8 feat(compare): migrate compare charts to echarts_timeseries_line and drop the legacy plugin (#41738)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:57 -07:00
Evan Rusackas
b2849d826a feat(deck-multi): migrate deck_multi chart to the v1 chart data API (#41730)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:57 -07:00
Evan Rusackas
2cd9873540 feat(bubble): migrate saved bubble charts to bubble_v2 and drop the legacy plugin (#41728)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:56 -07:00
Evan Rusackas
8948b5af94 fix(legacy-viz): restore line-engine default ordering for horizon and rose (#41732)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:55 -07:00
Evan Rusackas
3bcb003eeb feat(partition): migrate partition chart to v1 chart data API (#41729)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:54 -07:00
Claude Code
480fbe0e06 chore(viz): update SCOPE tracker after tier 2 completion
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:54:54 -07:00
Evan Rusackas
3abbe1c84a feat(time-pivot): migrate time_pivot chart to v1 chart data API (#41727)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:53 -07:00
Evan Rusackas
311b0087f3 feat(rose): migrate rose chart to v1 chart data API (#41726)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:53 -07:00
Evan Rusackas
0fea3c88a5 feat(horizon): migrate horizon chart to v1 chart data API (#41725)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:53 -07:00
Claude Code
e61610bd98 chore(viz): update SCOPE tracker after tier 1-2 merges
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:54:52 -07:00
Evan Rusackas
9729306c06 feat(time-table): migrate time_table chart to v1 chart data API (#41723)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:51 -07:00
Evan Rusackas
93a02449db feat(world-map): migrate world_map chart to v1 chart data API (#41720)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:51 -07:00
Evan Rusackas
cbbcf1bed2 feat(calendar): migrate cal_heatmap chart to v1 chart data API (#41724)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:51 -07:00
Evan Rusackas
218c64296f feat(paired-t-test): migrate paired_ttest chart to v1 chart data API (#41721)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:50 -07:00
Evan Rusackas
39eced2c2e feat(chord): migrate chord chart to v1 chart data API (#41719)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:50 -07:00
Claude Code
bef4680ffd chore(viz): update SCOPE tracker after phase 0 + tier 1 merges
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:54:50 -07:00
Evan Rusackas
5870259219 feat(bullet): migrate bullet chart to v1 chart data API (#41718)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:50 -07:00
Evan Rusackas
a4bcd2cc87 feat(parallel-coordinates): migrate para chart to v1 chart data API (#41716)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:50 -07:00
Evan Rusackas
f8dd65cc0b feat(country-map): migrate country_map chart to v1 chart data API (#41717)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:50 -07:00
Evan Rusackas
b1bc8fa8f2 chore(viz): phase 0 — remove orphaned viz.py classes and nvd3 BoxPlot leftovers (#41715)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-27 14:54:49 -07:00
Claude Code
5d86215a9d chore(viz): add SCOPE/UPDATES trackers for legacy viz pipeline removal
Working documents for the remove-legacy-viz-pipeline feature branch;
stripped before final merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:54:49 -07:00
610 changed files with 12090 additions and 17601 deletions

2
.github/CODEOWNERS vendored
View File

@@ -34,7 +34,7 @@
**/*.geojson @villebro @rusackas
**/*.ipynb @villebro @rusackas
/superset-frontend/plugins/legacy-plugin-chart-country-map/ @villebro @rusackas
/superset-frontend/plugins/plugin-chart-country-map/ @villebro @rusackas
# Notify translation maintainers of changes to translations

1
.gitignore vendored
View File

@@ -138,6 +138,7 @@ PROJECT.md
.aider*
.claude_rc*
.claude/settings.local.json
.claude/worktrees/
.env.local
oxc-custom-build/
*.code-workspace

View File

@@ -193,6 +193,10 @@ are the intended model going forward; deprecating and removing implicit viewersh
in a later major version.
- [41044](https://github.com/apache/superset/issues/41044): Removes the deprecated `AVOID_COLORS_COLLISION` feature flag (it defaulted to `True`). Color-collision avoidance is now permanently enabled; any config override setting it to `False` is ignored.
- [41714](https://github.com/apache/superset/pull/41714): **Breaking — the legacy `explore_json` chart-data pipeline is removed** at its long-declared `5.0.0` EOL. The `/superset/explore_json/` and `/superset/explore_json/data/<cache_key>` endpoints, `superset/viz.py`, the `Slice.viz` property, the `get_viz` factory, the `load_explore_json_into_cache` celery task and the `viz=` overload of `security_manager.raise_for_access` are gone. Anything importing `superset.viz` must migrate to the QueryContext / `pandas_postprocessing` pipeline behind `/api/v1/chart/data`. All 15 remaining legacy charts were migrated first: most keep their `viz_type` and renderer (no action needed for saved charts), while saved nvd3 Bubble charts are auto-migrated to the ECharts Bubble Chart (`bubble_v2`) and saved "Time-series Percent Change" (`compare`) charts to the ECharts Line Chart — the nvd3 renderer's interactive percent re-basing is not preserved. The deck.gl Multiple Layers chart now fetches its layers entirely client-side; its initial autozoom falls back to the saved viewport, and dashboard filter badges no longer aggregate child-layer filter metadata.
- [41714](https://github.com/apache/superset/pull/41714): Charts migrated in place keep a `NULL` saved query context until they are next opened in Explore (which regenerates it automatically) or re-saved. Until then, cache warm-up and annotation layers referencing such a chart report an actionable error rather than warming/rendering; opening the chart once resolves it.
- [41714](https://github.com/apache/superset/pull/41714): **Breaking for third-party viz plugins** — the `useLegacyApi` field of `ChartMetadata` in `@superset-ui/core` is removed. Plugins that set it must provide a `buildQuery` and consume `/api/v1/chart/data`. The migrated first-party packages also drop their `legacy-` prefix: `@superset-ui/legacy-plugin-chart-{calendar,chord,country-map,horizon,paired-t-test,parallel-coordinates,partition,rose,world-map}``@superset-ui/plugin-chart-*`, and `@superset-ui/legacy-preset-chart-nvd3``@superset-ui/preset-chart-nvd3`. The `can_explore_json` permission is no longer created or granted; custom roles referencing it should switch to the `can_read` permissions on `Chart`.
- [41813](https://github.com/apache/superset/pull/41813): `redis` (the Python client, `redis-py`) is bumped from 5.3.1 to 8.0.1. redis-py 8 changes several connection defaults; Superset's own Redis-backed features (`GLOBAL_ASYNC_QUERIES_CACHE_BACKEND`, `DISTRIBUTED_COORDINATION_CONFIG`, and the MCP Redis store) explicitly pin the pre-upgrade behavior so this bump is a no-op for them: the wire protocol stays RESP2 (not the new RESP3 default, which requires Redis/Sentinel 6+ to speak `HELLO`) and there is still no socket timeout by default (redis-py 8 defaults to 5s, which could otherwise newly time out large cached payloads or slow networks). The no-timeout default can now be overridden via two new config keys, `CACHE_REDIS_SOCKET_TIMEOUT` / `CACHE_REDIS_SOCKET_CONNECT_TIMEOUT`, on any `CacheConfig` dict using `CACHE_TYPE: RedisCache` or `RedisSentinelCache`. Separately, redis-py 6+ changed the default for `ssl_check_hostname` from `False` to `True` for SSL connections using `ssl_cert_reqs="required"` (the default) — this is a security improvement, so it has **not** been reverted; deployments with `CACHE_REDIS_SSL=True` whose certificates lack a hostname matching the connection address should set `CACHE_REDIS_SSL_CERT_REQS="none"` (disables cert verification entirely, matching hostname-check bypass) or replace the certificate. General-purpose cache/results backends configured via `CACHE_CONFIG` / `DATA_CACHE_CONFIG` / `RESULTS_BACKEND` with `CACHE_TYPE: RedisCache` go through `flask-caching`'s own Redis backend (outside Superset's code) and are subject to the same new defaults; pass `socket_timeout` / `protocol` via `CACHE_OPTIONS` there if needed. Celery broker and result-backend connections (built by `kombu`, also outside Superset's code) keep their no-socket-timeout behavior (`kombu` passes `socket_timeout=None` explicitly) but do **not** pin the wire protocol, so they follow redis-py's RESP3 default — which requires a Redis server new enough to speak `HELLO` (Redis 6+). Deployments using a pre-6.0 Redis server (EOL) as a Celery broker should upgrade the server before taking this bump.
@@ -212,7 +216,7 @@ in a later major version.
- **`SqlaTable.sql_url` query-string format.** `SqlaTable.sql_url` now URL-encodes `table_name` and joins it as a query parameter rather than concatenating a second `?`. Previously, with `Database.sql_url` returning `/sqllab/?dbid=<id>`, the concatenation produced `/sqllab/?dbid=<id>?table_name=<raw>` — a malformed second `?` that broke the query parser. External code that parsed the legacy `<base>?table_name=<raw>` shape now sees properly percent-encoded values (e.g. `/``%2F`, ` ``+` or `%20`); decode with `urllib.parse.parse_qsl`.
- **New config flag `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE` (default `False`).** Share/permalink URLs now substitute `window.location.origin` for the backend-supplied origin so a proxied or subdirectory-deployed Superset never hands the user an unreachable internal hostname. Operators whose reverse proxy correctly forwards `X-Forwarded-Host` *and* who want permalinks to carry the backend's literal origin can opt out by setting `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE = True` in `superset_config.py`. Default `False` (rewrite is on); flipping the default would regress the dominant proxied/subdir deployment to an unreachable host.
- **New config flag `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE` (default `False`).** Share/permalink URLs now substitute `window.location.origin` for the backend-supplied origin so a proxied or subdirectory-deployed Superset never hands the user an unreachable internal hostname. Operators whose reverse proxy correctly forwards `X-Forwarded-Host` _and_ who want permalinks to carry the backend's literal origin can opt out by setting `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE = True` in `superset_config.py`. Default `False` (rewrite is on); flipping the default would regress the dominant proxied/subdir deployment to an unreachable host.
- [41651](https://github.com/apache/superset/pull/41651): **New do-not-translate standard for translation catalogs.** Strings that must stay identical to the source — icon names (e.g. `bolt`), enum/option values (`step-after`), SQL keywords, API field names (`error_message`), code constants, and example placeholders — are now marked with a `#. do-not-translate` extracted comment. The list lives in the `superset/translations/do-not-translate.txt` registry; `scripts/translations/apply_do_not_translate.py` stamps the marker onto `messages.pot` during `babel_update.sh`, and `pybabel update` propagates it to every `.po`, so the status is consistent across all languages. The AI backfill (`backfill_po.py`) and translators leave these entries untranslated (source fallback). The legacy per-catalog convention (a `# Не переводить` translator comment in the `ru` catalog) is still honored for back-compat but is superseded by this standard; contributors adding new machine-read strings should add the msgid to the registry rather than annotating individual catalogs.
@@ -249,7 +253,7 @@ Theme tokens are unaffected — antd 6 removed none of the tokens Superset expos
### Guest-token RLS rules reject unknown fields
The `rls` rules passed to `POST /api/v1/security/guest_token/` are now validated strictly: a rule may only contain `dataset` and `clause`. Previously unknown fields were silently dropped, so a mistyped or legacy scope key (most commonly `datasource` instead of `dataset`) produced a rule with no `dataset`, which is treated as a *global* rule applied to every dataset the embedded resource can reach. Such a request now returns HTTP 400 identifying the offending field instead of issuing a token with an unintended global rule. Integrators that were sending extra fields in RLS rules must remove them; valid dataset-scoped (`{"dataset": 41, "clause": "..."}`) and global (`{"clause": "..."}`) rules are unaffected.
The `rls` rules passed to `POST /api/v1/security/guest_token/` are now validated strictly: a rule may only contain `dataset` and `clause`. Previously unknown fields were silently dropped, so a mistyped or legacy scope key (most commonly `datasource` instead of `dataset`) produced a rule with no `dataset`, which is treated as a _global_ rule applied to every dataset the embedded resource can reach. Such a request now returns HTTP 400 identifying the offending field instead of issuing a token with an unintended global rule. Integrators that were sending extra fields in RLS rules must remove them; valid dataset-scoped (`{"dataset": 41, "clause": "..."}`) and global (`{"clause": "..."}`) rules are unaffected.
### MCP service requires `MCP_JWT_AUDIENCE` when JWT auth is enabled
@@ -333,6 +337,7 @@ ALTER TABLE tagged_object DROP CONSTRAINT <constraint_name>;
-- MySQL: find names via `SHOW CREATE TABLE tagged_object;`
ALTER TABLE tagged_object DROP FOREIGN KEY <constraint_name>;
```
### Entity version-history infrastructure (gated off by default)
Introduces the schema and SQLAlchemy-Continuum wiring that captures version history for charts, dashboards, and datasets, plus read-only `GET /api/v1/{chart,dashboard,dataset}/<uuid>/versions/` endpoints. This ships **inert**: a new config flag `ENABLE_VERSIONING_CAPTURE` defaults to `False`, so no save writes any version rows and the endpoints return empty. It is an operational kill-switch (a release toggle that becomes a permanent ops switch), not a feature flag — set it to `True` to enable capture once validated. The migration is additive; existing entity `PUT` responses gain `old_version_uuid` / `new_version_uuid` body fields and an `ETag` header (both null/absent when capture is off).
@@ -349,12 +354,12 @@ These are behavior changes that take effect on upgrade regardless of `ENABLE_VER
A read-only companion to the version-history endpoints: each entity type gains a `GET /api/v1/{chart,dashboard,dataset}/<uuid>/activity/` endpoint returning a chronological, access-filtered stream of edits — the entity's own edits plus, for charts and dashboards, transitive edits to related entities during their association windows. Datasets have no related layer in V2, so `include=related` returns an empty stream for a dataset and `include=all` reduces to the dataset's own edits.
| Param | Type | Default | Purpose |
|---|---|---|---|
| `since` / `until` | ISO 8601 | — | Bound `issued_at` |
| `include` | `self` \| `related` \| `all` | `all` | Own edits, related edits, or both |
| `q` | string | — | Case-insensitive search over the full history, applied before pagination (so `count` reflects matches) |
| `page` / `page_size` | integer | `0` / `25` | Pagination (`page_size` clamped to 200) |
| Param | Type | Default | Purpose |
| -------------------- | ---------------------------- | ---------- | ------------------------------------------------------------------------------------------------------ |
| `since` / `until` | ISO 8601 | — | Bound `issued_at` |
| `include` | `self` \| `related` \| `all` | `all` | Own edits, related edits, or both |
| `q` | string | — | Case-insensitive search over the full history, applied before pagination (so `count` reflects matches) |
| `page` / `page_size` | integer | `0` / `25` | Pagination (`page_size` clamped to 200) |
Authorization reuses the resource's `can_read` permission and per-object `raise_for_access`; related-entity rows are visibility-filtered to what the caller may see. The stream is empty unless version capture is on (`ENABLE_VERSIONING_CAPTURE`).
@@ -415,6 +420,7 @@ Operators can tune or disable the policy via config:
### Data uploads bounded by UPLOAD_MAX_FILE_SIZE_BYTES
Single data-file uploads (CSV, Excel, columnar) are now bounded by the `UPLOAD_MAX_FILE_SIZE_BYTES` config option, which defaults to `100 * 1024 * 1024` (100 MB). Files larger than this are rejected with a `413` before their contents are buffered into memory. Set `UPLOAD_MAX_FILE_SIZE_BYTES = None` to disable the check and restore unbounded uploads.
### Currency symbol position follows the locale when unset
When a chart's currency control leaves the **Prefix or suffix** field empty, the currency symbol position is now derived from the deployment locale's own convention via `Intl.NumberFormat` instead of always defaulting to a suffix. For example, under the default `en-US` locale `USD`, `GBP`, and `EUR` render as a prefix (`$ 1,000`), while eurozone locales such as `fr-FR` render `EUR` as a suffix (`1 000 €`). An explicit Prefix/Suffix selection is always honored and is unaffected.
@@ -540,7 +546,7 @@ SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes"
```bash
superset re-encrypt-secrets --engine aes-gcm
```
A live instance keeps writing *new* secrets as AES-CBC during the window between step 2 and the restart in step 4; this second pass sweeps those up (it is idempotent, so already-migrated values are skipped).
A live instance keeps writing _new_ secrets as AES-CBC during the window between step 2 and the restart in step 4; this second pass sweeps those up (it is idempotent, so already-migrated values are skipped).
Schedule the cutover in a quiet window. Runtime reads use only the single configured engine, so in a multi-worker deployment there is an unavoidable brief decrypt-outage between the migration commit and the last worker restarting with the new config — each migrator run is transactional, but the fleet-wide cutover is not zero-downtime.
@@ -568,11 +574,11 @@ With the flag enabled: `DELETE /api/v1/dataset/<id>` no longer hard-deletes the
**Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_tables_deleted_at`) to the `tables` table. The column add is instant; the index build runs inline (no `CONCURRENTLY`) and may briefly block writes on the `tables` table (INSERT/UPDATE/DELETE are queued while the index builds; reads are unaffected) on large Postgres deployments. MySQL InnoDB builds the index online (no blocking). Production deployments with many thousands of datasets should run this migration during a maintenance window.
**Rollback note:** if the application code is rolled back after datasets have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted datasets silently become live, active datasets with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) *before* downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
**Rollback note:** if the application code is rolled back after datasets have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted datasets silently become live, active datasets with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) _before_ downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
**SQL Lab / dataset-creation flows:** creating a dataset over a table whose dataset sits in the trash is refused. The SQL Lab "save as dataset" flow (`get_or_create_dataset`) and file uploads return a **422 naming the hidden twin and the restore endpoint**; the plain create, update, and duplicate paths currently fail with the generic "already exists" 422. In all cases the remediation is the same: restore the hidden dataset (or use a different table name). Perm-string maintenance also covers hidden rows: renaming a database rewrites `perm`/`schema_perm`/`catalog_perm` on soft-deleted datasets and their charts, so a later restore does not resurrect stale permission strings.
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-editors get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored _and_ has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-editors get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Uniqueness-validation changes that apply regardless of the feature flag:** two dataset uniqueness checks were tightened alongside this work and are active even with `SOFT_DELETE` off. (1) Create/update uniqueness treats a dataset whose `catalog` is `NULL` as belonging to the database's default catalog, so a legacy twin pair (`catalog=NULL` vs. `catalog=<default>`, same database/schema/name) that older versions allowed now fails validation with "already exists" when either row is edited — resolve by renaming or removing one of the twins. (2) Duplicating a dataset now checks name collisions scoped to the target (database, catalog, schema) instead of globally by name alone: duplicates into other databases that were previously blocked are now allowed.
@@ -592,9 +598,9 @@ With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the ch
**Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_slices_deleted_at`) to the `slices` table. The column add is instant; the index build runs inline (no `CONCURRENTLY`) and may briefly block writes on the `slices` table (INSERT/UPDATE/DELETE are queued while the index builds; reads are unaffected) on large Postgres deployments. MySQL InnoDB builds the index online (no blocking).
**Rollback note:** if the application code is rolled back after charts have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted charts silently become live, active charts with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) *before* downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
**Rollback note:** if the application code is rolled back after charts have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted charts silently become live, active charts with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) _before_ downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored _and_ has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
- [39914](https://github.com/apache/superset/pull/39914) `ALERT_REPORT_SLACK_V2` now defaults to `True` and the legacy Slack v1 integration (`Slack` recipient type, `files.upload` API) is deprecated for removal in the next major. Slack blocked new apps from `files.upload` in May 2024 and fully retired the method for all apps on November 12, 2025; because the v1 path sends files through `files.upload`, v1 file-bearing sends now fail at the API level — only text-only `chat_postMessage` still works via the legacy path. Grant your Slack bot the `channels:read` and `groups:read` scopes so existing `Slack` recipients can be auto-upgraded to `SlackV2` on next send. Operators who explicitly override the flag to `False`, or whose Slack bot is missing those scopes, will see deprecation warnings while text-only sends continue through the legacy path.
@@ -618,7 +624,7 @@ The partial-index replacement is dialect-dependent: PostgreSQL uses a native `WH
**Slug semantics:** on PostgreSQL and MySQL 8.0.13+, the slug of a soft-deleted dashboard is **free for reuse**. A new active dashboard can claim it immediately. Restoring a soft-deleted dashboard whose slug has since been claimed returns **422 with a clean error** (`DashboardSlugConflictError`) — rename one of the dashboards and retry; the restore is not silently rejected by a database-level constraint violation.
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, editor/viewer subjects, tags). Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and editorship of the deleted row (or admin) — non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored _and_ has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, editor/viewer subjects, tags). Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and editorship of the deleted row (or admin) — non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
### Engine spec capability flag: `supports_offset`
@@ -630,10 +636,10 @@ A new `BaseEngineSpec.supports_offset` attribute (default `True`) indicates whet
A new feature flag `GRANULAR_EXPORT_CONTROLS` introduces three fine-grained permissions that replace the legacy `can_csv` permission:
| Permission | Controls |
|---|---|
| `can_export_data` | CSV, Excel, JSON exports |
| `can_export_image` | Screenshot/PDF exports |
| Permission | Controls |
| -------------------- | ---------------------------- |
| `can_export_data` | CSV, Excel, JSON exports |
| `can_export_image` | Screenshot/PDF exports |
| `can_copy_clipboard` | Copy-to-clipboard operations |
When the feature flag is enabled, these permissions are enforced on both the frontend (disabled buttons with tooltips) and backend (403 responses from API endpoints). When disabled, legacy `can_csv` behavior is preserved.
@@ -671,14 +677,17 @@ The Kenya country map has been updated to reflect the 47 counties established un
MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging:
**Two-layer instrumentation:**
1. **Middleware layer** (`LoggingMiddleware`): Automatically logs all MCP tool calls with `duration_ms` and `success` status in the audit log (Action Log UI, logs table)
2. **Sub-operation tracking**: All 19 MCP tools include granular `event_logger.log_context()` blocks for tracking individual operations like validation, database writes, and query execution
**Action naming convention:**
- Tool-level logs: `mcp_tool_call` (via middleware)
- Sub-operation logs: `mcp.{tool_name}.{operation}` (e.g., `mcp.generate_chart.validation`, `mcp.execute_sql.query_execution`)
**Querying MCP logs:**
```sql
-- Top slowest MCP operations
SELECT action, COUNT(*) as calls, AVG(duration_ms) as avg_ms
@@ -713,6 +722,7 @@ A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-b
The distributed coordination is used by the Global Task Framework (GTF) for abort notifications and task completion signaling, and will eventually replace `GLOBAL_ASYNC_QUERIES_CACHE_BACKEND` as the standard signaling backend. Configuring this is recommended for Redis enabled production deployments.
Example configuration in `superset_config.py`:
```python
DISTRIBUTED_COORDINATION_CONFIG = {
"CACHE_TYPE": "RedisCache",
@@ -727,9 +737,11 @@ See `superset/config.py` for complete configuration options.
### WebSocket config for GAQ with Docker
[35896](https://github.com/apache/superset/pull/35896) and [37624](https://github.com/apache/superset/pull/37624) updated documentation on how to run and configure Superset with Docker. Specifically for the WebSocket configuration, a new `docker/superset-websocket/config.example.json` was added to the repo, so that users could copy it to create a `docker/superset-websocket/config.json` file. The existing `docker/superset-websocket/config.json` was removed and git-ignored, so if you're using GAQ / WebSocket make sure to:
- Stash/backup your existing `config.json` file, to re-apply it after (will get git-ignored going forward)
- Update the `volumes` configuration for the `superset-websocket` service in your `docker-compose.override.yml` file, to include the `docker/superset-websocket/config.json` file. For example:
``` yaml
```yaml
services:
superset-websocket:
volumes:
@@ -742,7 +754,9 @@ services:
### Example Data Loading Improvements
#### New Directory Structure
Examples are now organized by name with data and configs co-located:
```
superset/examples/
├── _shared/ # Shared database & metadata configs
@@ -755,12 +769,14 @@ superset/examples/
```
#### Simplified Parquet-based Loading
- Auto-discovery: create `superset/examples/my_dataset/data.parquet` to add a new example
- Parquet is an Apache project format: compressed (~27% smaller), self-describing schema
- YAML configs define datasets, charts, and dashboards declaratively
- Removed Python-based data generation from individual example files
#### Test Data Reorganization
- Moved `big_data.py` to `superset/cli/test_loaders.py` - better reflects its purpose as a test utility
- Fixed inverted logic for `--load-test-data` flag (now correctly includes .test.yaml files when flag is set)
- Clarified CLI flags:
@@ -770,6 +786,7 @@ superset/examples/
- `--load-big-data` / `-b`: Generate synthetic stress-test data
#### Bug Fixes
- Fixed numpy array serialization for PostgreSQL (converts complex types to JSON strings)
- Fixed KeyError for `allow_csv_upload` field in database configs (now optional with default)
- Fixed test data loading logic that was incorrectly filtering files
@@ -779,6 +796,7 @@ superset/examples/
The MCP (Model Context Protocol) service enables AI assistants and automation tools to interact programmatically with Superset.
#### New Features
- MCP service infrastructure with FastMCP framework
- Tools for dashboards, charts, datasets, SQL Lab, and instance metadata
- Optional dependency: install with `pip install apache-superset[fastmcp]`
@@ -788,6 +806,7 @@ The MCP (Model Context Protocol) service enables AI assistants and automation to
#### New Configuration Options
**Development** (single-user, local testing):
```python
# superset_config.py
MCP_DEV_USERNAME = "admin" # User for MCP authentication
@@ -796,6 +815,7 @@ MCP_SERVICE_PORT = 5008
```
**Production** (JWT-based, multi-user):
```python
# superset_config.py
MCP_AUTH_ENABLED = True
@@ -841,12 +861,14 @@ superset mcp run --port 5008 --use-factory-config
The MCP service runs as a **separate process** from the Superset web server.
**Important**:
- Requires same Python environment and configuration as Superset
- Shares database connections with main Superset app
- Can be scaled independently from web server
- Requires `fastmcp` package (optional dependency)
**Installation**:
```bash
# Install with MCP support
pip install apache-superset[fastmcp]
@@ -860,6 +882,7 @@ Use systemd, supervisord, or Kubernetes to manage the MCP service process.
See `superset/mcp_service/PRODUCTION.md` for deployment guides.
**Security**:
- Development: Uses `MCP_DEV_USERNAME` for single-user access
- Production: **MUST** configure JWT authentication
- See `superset/mcp_service/SECURITY.md` for details
@@ -879,8 +902,10 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
### Breaking Changes
- [37370](https://github.com/apache/superset/pull/37370): The `APP_NAME` configuration variable no longer controls the browser window/tab title or other frontend branding. Application names should now be configured using the theme system with the `brandAppName` token. The `APP_NAME` config is still used for backend contexts (MCP service, logs, etc.) and serves as a fallback if `brandAppName` is not set.
- **Migration:**
```python
# Before (Superset 5.x)
APP_NAME = "My Custom App"
@@ -924,16 +949,16 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
Eight M:N association tables move from a synthetic `id INTEGER PRIMARY KEY` to a composite `PRIMARY KEY (fk1, fk2)` on their two foreign-key columns. The surrogate `id` is dropped, and the redundant `UNIQUE (fk1, fk2)` on the two tables that carried one is removed (now subsumed by the PK).
| Table | Composite PK |
|---|---|
| `dashboard_roles` | `(dashboard_id, role_id)` |
| `dashboard_slices` | `(dashboard_id, slice_id)` |
| `dashboard_user` | `(user_id, dashboard_id)` |
| Table | Composite PK |
| ---------------------- | ------------------------------- |
| `dashboard_roles` | `(dashboard_id, role_id)` |
| `dashboard_slices` | `(dashboard_id, slice_id)` |
| `dashboard_user` | `(user_id, dashboard_id)` |
| `report_schedule_user` | `(user_id, report_schedule_id)` |
| `rls_filter_roles` | `(role_id, rls_filter_id)` |
| `rls_filter_tables` | `(table_id, rls_filter_id)` |
| `slice_user` | `(user_id, slice_id)` |
| `sqlatable_user` | `(user_id, table_id)` |
| `rls_filter_roles` | `(role_id, rls_filter_id)` |
| `rls_filter_tables` | `(table_id, rls_filter_id)` |
| `slice_user` | `(user_id, slice_id)` |
| `sqlatable_user` | `(user_id, table_id)` |
**Before upgrading:**
@@ -944,6 +969,7 @@ Eight M:N association tables move from a synthetic `id INTEGER PRIMARY KEY` to a
For large `dashboard_slices` / `report_schedule_user` tables, see the operator runbook in [#39859](https://github.com/apache/superset/pull/39859) — pre-flight inventory queries, per-dialect lock-window sizing, and the duplicate / NULL-FK roll-up — to plan the maintenance window.
## 6.0.0
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
@@ -952,8 +978,8 @@ For large `dashboard_slices` / `report_schedule_user` tables, see the operator r
- Change any hex color values to one of: `"success"`, `"processing"`, `"error"`, `"warning"`, `"default"`
- Custom colors are no longer supported to maintain consistency with Ant Design components
- [34561](https://github.com/apache/superset/pull/34561) Added tiled screenshot functionality for Playwright-based reports to handle large dashboards more efficiently. When enabled (default: `SCREENSHOT_TILED_ENABLED = True`), dashboards with 20+ charts or height exceeding 5000px will be captured using multiple viewport-sized tiles and combined into a single image. This improves report generation performance and reliability for large dashboards.
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
- [33084](https://github.com/apache/superset/pull/33084) The DISALLOWED_SQL_FUNCTIONS configuration now includes additional potentially sensitive database functions across PostgreSQL, MySQL, SQLite, MS SQL Server, and ClickHouse. Existing queries using these functions may now be blocked. Review your SQL Lab queries and dashboards if you encounter "disallowed function" errors after upgrading
- [34235](https://github.com/apache/superset/pull/34235) CSV exports now use `utf-8-sig` encoding by default to include a UTF-8 BOM, improving compatibility with Excel.
- [34258](https://github.com/apache/superset/pull/34258) changing the default in Dockerfile to INCLUDE_CHROMIUM="false" (from "true") in the past. This ensures the `lean` layer is lean by default, and people can opt-in to the `chromium` layer by setting the build arg `INCLUDE_CHROMIUM=true`. This is a breaking change for anyone using the `lean` layer, as it will no longer include Chromium by default.

View File

@@ -18,7 +18,7 @@ code is less ambiguous and is unique to all regions in the world.
## Included Maps
The current list of countries can be found in the src
[legacy-plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts)
[plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/plugin-chart-country-map/src/countries.ts)
The Country Maps visualization already ships with the maps for the following countries:
@@ -31,10 +31,10 @@ The Country Maps visualization already ships with the maps for the following cou
## Adding a New Country
To add a new country to the list, you'd have to edit files in
[@superset-ui/legacy-plugin-chart-country-map](https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-country-map).
[@superset-ui/plugin-chart-country-map](https://github.com/apache/superset/tree/master/superset-frontend/plugins/plugin-chart-country-map).
1. Generate a new GeoJSON file for your country following the guide in [this Jupyter notebook](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/scripts/Country%20Map%20GeoJSON%20Generator.ipynb).
2. Edit the countries list in [legacy-plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts).
1. Generate a new GeoJSON file for your country following the guide in [this Jupyter notebook](https://github.com/apache/superset/blob/master/superset-frontend/plugins/plugin-chart-country-map/scripts/Country%20Map%20GeoJSON%20Generator.ipynb).
2. Edit the countries list in [plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/plugin-chart-country-map/src/countries.ts).
3. Install superset-frontend dependencies: `cd superset-frontend && npm install`
4. Verify your countries in Superset plugins storybook: `npm run plugins:storybook`.
5. Build and install Superset from source code.

View File

@@ -51,24 +51,22 @@
"@scarf/scarf": "^1.4.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
"@superset-ui/core": "file:./packages/superset-ui-core",
"@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar",
"@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord",
"@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map",
"@superset-ui/legacy-plugin-chart-horizon": "file:./plugins/legacy-plugin-chart-horizon",
"@superset-ui/legacy-plugin-chart-paired-t-test": "file:./plugins/legacy-plugin-chart-paired-t-test",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "file:./plugins/legacy-plugin-chart-parallel-coordinates",
"@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition",
"@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose",
"@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map",
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
"@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table",
"@superset-ui/plugin-chart-calendar": "file:./plugins/plugin-chart-calendar",
"@superset-ui/plugin-chart-cartodiagram": "file:./plugins/plugin-chart-cartodiagram",
"@superset-ui/plugin-chart-chord": "file:./plugins/plugin-chart-chord",
"@superset-ui/plugin-chart-country-map": "file:./plugins/plugin-chart-country-map",
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
"@superset-ui/plugin-chart-horizon": "file:./plugins/plugin-chart-horizon",
"@superset-ui/plugin-chart-paired-t-test": "file:./plugins/plugin-chart-paired-t-test",
"@superset-ui/plugin-chart-parallel-coordinates": "file:./plugins/plugin-chart-parallel-coordinates",
"@superset-ui/plugin-chart-partition": "file:./plugins/plugin-chart-partition",
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
"@superset-ui/plugin-chart-point-cluster-map": "file:./plugins/plugin-chart-point-cluster-map",
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
"@superset-ui/plugin-chart-world-map": "file:./plugins/plugin-chart-world-map",
"@superset-ui/preset-chart-deckgl": "file:./plugins/preset-chart-deckgl",
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
"@types/d3-format": "^3.0.1",
@@ -10770,54 +10768,26 @@
"resolved": "packages/generator-superset",
"link": true
},
"node_modules/@superset-ui/legacy-plugin-chart-calendar": {
"resolved": "plugins/legacy-plugin-chart-calendar",
"link": true
},
"node_modules/@superset-ui/legacy-plugin-chart-chord": {
"resolved": "plugins/legacy-plugin-chart-chord",
"link": true
},
"node_modules/@superset-ui/legacy-plugin-chart-country-map": {
"resolved": "plugins/legacy-plugin-chart-country-map",
"link": true
},
"node_modules/@superset-ui/legacy-plugin-chart-horizon": {
"resolved": "plugins/legacy-plugin-chart-horizon",
"link": true
},
"node_modules/@superset-ui/legacy-plugin-chart-paired-t-test": {
"resolved": "plugins/legacy-plugin-chart-paired-t-test",
"link": true
},
"node_modules/@superset-ui/legacy-plugin-chart-parallel-coordinates": {
"resolved": "plugins/legacy-plugin-chart-parallel-coordinates",
"link": true
},
"node_modules/@superset-ui/legacy-plugin-chart-partition": {
"resolved": "plugins/legacy-plugin-chart-partition",
"link": true
},
"node_modules/@superset-ui/legacy-plugin-chart-rose": {
"resolved": "plugins/legacy-plugin-chart-rose",
"link": true
},
"node_modules/@superset-ui/legacy-plugin-chart-world-map": {
"resolved": "plugins/legacy-plugin-chart-world-map",
"link": true
},
"node_modules/@superset-ui/legacy-preset-chart-nvd3": {
"resolved": "plugins/legacy-preset-chart-nvd3",
"link": true
},
"node_modules/@superset-ui/plugin-chart-ag-grid-table": {
"resolved": "plugins/plugin-chart-ag-grid-table",
"link": true
},
"node_modules/@superset-ui/plugin-chart-calendar": {
"resolved": "plugins/plugin-chart-calendar",
"link": true
},
"node_modules/@superset-ui/plugin-chart-cartodiagram": {
"resolved": "plugins/plugin-chart-cartodiagram",
"link": true
},
"node_modules/@superset-ui/plugin-chart-chord": {
"resolved": "plugins/plugin-chart-chord",
"link": true
},
"node_modules/@superset-ui/plugin-chart-country-map": {
"resolved": "plugins/plugin-chart-country-map",
"link": true
},
"node_modules/@superset-ui/plugin-chart-echarts": {
"resolved": "plugins/plugin-chart-echarts",
"link": true
@@ -10826,6 +10796,22 @@
"resolved": "plugins/plugin-chart-handlebars",
"link": true
},
"node_modules/@superset-ui/plugin-chart-horizon": {
"resolved": "plugins/plugin-chart-horizon",
"link": true
},
"node_modules/@superset-ui/plugin-chart-paired-t-test": {
"resolved": "plugins/plugin-chart-paired-t-test",
"link": true
},
"node_modules/@superset-ui/plugin-chart-parallel-coordinates": {
"resolved": "plugins/plugin-chart-parallel-coordinates",
"link": true
},
"node_modules/@superset-ui/plugin-chart-partition": {
"resolved": "plugins/plugin-chart-partition",
"link": true
},
"node_modules/@superset-ui/plugin-chart-pivot-table": {
"resolved": "plugins/plugin-chart-pivot-table",
"link": true
@@ -10842,6 +10828,10 @@
"resolved": "plugins/plugin-chart-word-cloud",
"link": true
},
"node_modules/@superset-ui/plugin-chart-world-map": {
"resolved": "plugins/plugin-chart-world-map",
"link": true
},
"node_modules/@superset-ui/preset-chart-deckgl": {
"resolved": "plugins/preset-chart-deckgl",
"link": true
@@ -16533,12 +16523,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/cephes": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/cephes/-/cephes-2.0.0.tgz",
"integrity": "sha512-4GMUzkcXHZ0HMZ3gZdBrv8pQs1/zkJh2Q9rQOF8NJZHanM359y3XOSdeqmDBPfxQKYQpJt58R3dUpofrIXJ2mg==",
"license": "BSD-3-Clause"
},
"node_modules/chai": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
@@ -18956,15 +18940,6 @@
"readable-stream": "^3.1.1"
}
},
"node_modules/distributions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/distributions/-/distributions-2.2.0.tgz",
"integrity": "sha512-n7ybud+CRAOZlpg+ETuA0PTiSBfyVNt8Okns5gSK4NvHwj7RamQoufptOucvVcTn9CV4DZ38p1k6TgwMexUNkQ==",
"license": "MIT",
"dependencies": {
"cephes": "^2.0.0"
}
},
"node_modules/dnd-core": {
"version": "11.1.3",
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz",
@@ -20819,12 +20794,6 @@
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
@@ -31505,15 +31474,6 @@
"fflate": "^0.8.0"
}
},
"node_modules/nvd3-fork": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/nvd3-fork/-/nvd3-fork-2.0.5.tgz",
"integrity": "sha512-Sq3q2rvR/9FJ35LVmqdQJAnfmD15BaIHSBg5wZZL/WLcq/nthff8ukabwFdbW0zeE1c/yPq+DKl6MxnUTR45DA==",
"license": "Apache-2.0",
"peerDependencies": {
"d3": "^3.4.4"
}
},
"node_modules/nwsapi": {
"version": "2.2.23",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
@@ -36050,15 +36010,6 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
"node_modules/reactable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reactable/-/reactable-1.1.0.tgz",
"integrity": "sha512-SnvZ3CXyFFxGotw9cqNiVUGb2oW16UlIypGQZRJGgPiJuFqW22jO7A+Y/Tvv8no8F/bZoLdZ+QJP7eZfcc9kCw==",
"license": "MIT",
"peerDependencies": {
"react": "* || ^0.14.0"
}
},
"node_modules/read": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/read/-/read-4.1.0.tgz",
@@ -44184,245 +44135,6 @@
"version": "0.20.3",
"license": "Apache-2.0"
},
"plugins/legacy-plugin-chart-calendar": {
"name": "@superset-ui/legacy-plugin-chart-calendar",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3-array": "^3.2.4",
"d3-selection": "^3.0.0",
"d3-tip": "^0.9.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@emotion/react": "^11.4.1",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/legacy-plugin-chart-calendar/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"plugins/legacy-plugin-chart-calendar/node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"plugins/legacy-plugin-chart-chord": {
"name": "@superset-ui/legacy-plugin-chart-chord",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"prop-types": "^15.8.1",
"react": "^19.2.7"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*"
}
},
"plugins/legacy-plugin-chart-chord/node_modules/react": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"plugins/legacy-plugin-chart-country-map": {
"name": "@superset-ui/legacy-plugin-chart-country-map",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"d3-array": "^3.2.4",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/legacy-plugin-chart-country-map/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"plugins/legacy-plugin-chart-horizon": {
"name": "@superset-ui/legacy-plugin-chart-horizon",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3-array": "^3.2.4",
"d3-scale": "^4.0.2",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/legacy-plugin-chart-horizon/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"plugins/legacy-plugin-chart-paired-t-test": {
"name": "@superset-ui/legacy-plugin-chart-paired-t-test",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"distributions": "^2.2.0",
"prop-types": "^15.8.1",
"reactable": "^1.1.0"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/legacy-plugin-chart-parallel-coordinates": {
"name": "@superset-ui/legacy-plugin-chart-parallel-coordinates",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3v3": "npm:d3@3.5.17",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/legacy-plugin-chart-partition": {
"name": "@superset-ui/legacy-plugin-chart-partition",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"d3-hierarchy": "^3.1.2",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@testing-library/jest-dom": "*",
"@testing-library/react": "^14.0.0",
"react": "^18.3.0",
"react-dom": "^18.3.0"
}
},
"plugins/legacy-plugin-chart-rose": {
"name": "@superset-ui/legacy-plugin-chart-rose",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"nvd3-fork": "^2.0.5",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@emotion/react": "^11.4.1",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/legacy-plugin-chart-world-map": {
"name": "@superset-ui/legacy-plugin-chart-world-map",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"d3-array": "^3.2.4",
"datamaps": "^0.5.10",
"prop-types": "^15.8.1",
"tinycolor2": "^1.6.0"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/legacy-plugin-chart-world-map/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"plugins/legacy-preset-chart-nvd3": {
"name": "@superset-ui/legacy-preset-chart-nvd3",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"d3-tip": "^0.9.1",
"dompurify": "^3.4.12",
"fast-safe-stringify": "^2.1.1",
"lodash": "^4.18.1",
"lodash-es": "^4.18.1",
"nvd3-fork": "^2.0.5",
"prop-types": "^15.8.1",
"urijs": "^1.19.11"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"dayjs": "^1.11.21",
"react": "^18.3.0"
}
},
"plugins/plugin-chart-ag-grid-table": {
"name": "@superset-ui/plugin-chart-ag-grid-table",
"version": "0.20.3",
@@ -44465,6 +44177,45 @@
"node": ">=12"
}
},
"plugins/plugin-chart-calendar": {
"name": "@superset-ui/plugin-chart-calendar",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3-array": "^3.2.4",
"d3-selection": "^3.0.0",
"d3-tip": "^0.9.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@emotion/react": "^11.4.1",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/plugin-chart-calendar/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"plugins/plugin-chart-calendar/node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"plugins/plugin-chart-cartodiagram": {
"name": "@superset-ui/plugin-chart-cartodiagram",
"version": "0.0.1",
@@ -44493,6 +44244,58 @@
"react-dom": "^18.3.0"
}
},
"plugins/plugin-chart-chord": {
"name": "@superset-ui/plugin-chart-chord",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"prop-types": "^15.8.1",
"react": "^19.2.7"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*"
}
},
"plugins/plugin-chart-chord/node_modules/react": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"plugins/plugin-chart-country-map": {
"name": "@superset-ui/plugin-chart-country-map",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"d3-array": "^3.2.4",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/plugin-chart-country-map/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"plugins/plugin-chart-echarts": {
"name": "@superset-ui/plugin-chart-echarts",
"version": "0.20.3",
@@ -44580,6 +44383,79 @@
"sprintf-js": ">= 1.1.1 < 2"
}
},
"plugins/plugin-chart-horizon": {
"name": "@superset-ui/plugin-chart-horizon",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3-array": "^3.2.4",
"d3-scale": "^4.0.2",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/plugin-chart-horizon/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"plugins/plugin-chart-paired-t-test": {
"name": "@superset-ui/plugin-chart-paired-t-test",
"version": "0.20.3",
"license": "Apache-2.0",
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/plugin-chart-parallel-coordinates": {
"name": "@superset-ui/plugin-chart-parallel-coordinates",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3v3": "npm:d3@3.5.17",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/plugin-chart-partition": {
"name": "@superset-ui/plugin-chart-partition",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"d3-hierarchy": "^3.1.2",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@testing-library/jest-dom": "*",
"@testing-library/react": "^14.0.0",
"react": "^18.3.0",
"react-dom": "^18.3.0"
}
},
"plugins/plugin-chart-pivot-table": {
"name": "@superset-ui/plugin-chart-pivot-table",
"version": "0.20.3",
@@ -44688,6 +44564,36 @@
"react": "^18.3.0"
}
},
"plugins/plugin-chart-world-map": {
"name": "@superset-ui/plugin-chart-world-map",
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"d3": "^3.5.17",
"d3-array": "^3.2.4",
"datamaps": "^0.5.10",
"prop-types": "^15.8.1",
"tinycolor2": "^1.6.0"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.3.0"
}
},
"plugins/plugin-chart-world-map/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"plugins/preset-chart-deckgl": {
"name": "@superset-ui/preset-chart-deckgl",
"version": "1.0.0",

View File

@@ -89,7 +89,7 @@
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80% --silent",
"test-loud": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80%",
"type": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" tsc --noEmit",
"update-maps": "cd plugins/legacy-plugin-chart-country-map/scripts && jupyter nbconvert --to notebook --execute --inplace --allow-errors --ExecutePreprocessor.timeout=1200 'Country Map GeoJSON Generator.ipynb'",
"update-maps": "cd plugins/plugin-chart-country-map/scripts && jupyter nbconvert --to notebook --execute --inplace --allow-errors --ExecutePreprocessor.timeout=1200 'Country Map GeoJSON Generator.ipynb'",
"validate-release": "../RELEASING/validate_this_release.sh"
},
"browserslist": [
@@ -136,16 +136,14 @@
"@scarf/scarf": "^1.4.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
"@superset-ui/core": "file:./packages/superset-ui-core",
"@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar",
"@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord",
"@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map",
"@superset-ui/legacy-plugin-chart-horizon": "file:./plugins/legacy-plugin-chart-horizon",
"@superset-ui/legacy-plugin-chart-paired-t-test": "file:./plugins/legacy-plugin-chart-paired-t-test",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "file:./plugins/legacy-plugin-chart-parallel-coordinates",
"@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition",
"@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose",
"@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map",
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
"@superset-ui/plugin-chart-calendar": "file:./plugins/plugin-chart-calendar",
"@superset-ui/plugin-chart-chord": "file:./plugins/plugin-chart-chord",
"@superset-ui/plugin-chart-country-map": "file:./plugins/plugin-chart-country-map",
"@superset-ui/plugin-chart-horizon": "file:./plugins/plugin-chart-horizon",
"@superset-ui/plugin-chart-paired-t-test": "file:./plugins/plugin-chart-paired-t-test",
"@superset-ui/plugin-chart-parallel-coordinates": "file:./plugins/plugin-chart-parallel-coordinates",
"@superset-ui/plugin-chart-partition": "file:./plugins/plugin-chart-partition",
"@superset-ui/plugin-chart-world-map": "file:./plugins/plugin-chart-world-map",
"@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table",
"@superset-ui/plugin-chart-cartodiagram": "file:./plugins/plugin-chart-cartodiagram",
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",

View File

@@ -0,0 +1,87 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ensureIsArray, getMetricLabel } from '@superset-ui/core';
import type { QueryFormMetric, QueryFormOrderBy } from '@superset-ui/core';
export interface BuildSortMetricOrderbyConfig {
/** The query's already-resolved metrics list. */
metrics: QueryFormMetric[];
/** The raw `timeseries_limit_metric` form-data value (single or multi). */
timeseriesLimitMetric?: QueryFormMetric | QueryFormMetric[] | null;
order_desc?: boolean;
/**
* Falls back to the first selected metric when no sort metric is set.
* Charts ported from a legacy viz whose query_obj always had a sort
* metric (defaulting to the first one) should set this; charts whose
* legacy query_obj left ordering absent without one should not.
*/
fallbackToFirstMetric?: boolean;
/**
* When true, only order when `order_desc` is set (matching legacy vizzes
* whose query_obj left the result unordered unless the operator asked
* for descending). When false, always order (ascending unless
* order_desc), matching legacy vizzes that ordered unconditionally.
*/
orderOnlyWhenDesc?: boolean;
}
export interface SortMetricOrderby {
/** `metrics`, with the sort metric appended if it wasn't already selected. */
metrics: QueryFormMetric[];
orderby: QueryFormOrderBy[];
}
/**
* Resolves a chart's sort metric and builds the corresponding query_obj
* `orderby`, appending the sort metric to `metrics` if it isn't already
* selected (so its value is present in the result to sort by). Several
* charts ported from the legacy chart-data pipeline share this exact
* shape with only the fallback/gating policy differing per their own
* legacy `query_obj` behavior -- see `fallbackToFirstMetric` and
* `orderOnlyWhenDesc`.
*/
export function buildSortMetricOrderby({
metrics,
timeseriesLimitMetric,
order_desc: orderDesc,
fallbackToFirstMetric = false,
orderOnlyWhenDesc = false,
}: BuildSortMetricOrderbyConfig): SortMetricOrderby {
const sortByMetric =
ensureIsArray(timeseriesLimitMetric)[0] ??
(fallbackToFirstMetric ? metrics[0] : undefined);
if (!sortByMetric) {
return { metrics, orderby: [] };
}
const sortByLabel = getMetricLabel(sortByMetric);
const nextMetrics = metrics.some(
metric => getMetricLabel(metric) === sortByLabel,
)
? metrics
: [...metrics, sortByMetric];
const shouldOrder = orderOnlyWhenDesc ? Boolean(orderDesc) : true;
return {
metrics: nextMetrics,
orderby: shouldOrder ? [[sortByMetric, !orderDesc]] : [],
};
}

View File

@@ -30,3 +30,4 @@ export * from './getTemporalColumns';
export * from './displayTimeRelatedControls';
export * from './colorControls';
export * from './metricColumnFilter';
export * from './buildSortMetricOrderby';

View File

@@ -0,0 +1,96 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { buildSortMetricOrderby } from '../../src';
test('is a no-op when there is no sort metric and no fallback', () => {
const result = buildSortMetricOrderby({
metrics: ['sum__num'],
timeseriesLimitMetric: undefined,
});
expect(result).toEqual({ metrics: ['sum__num'], orderby: [] });
});
test('falls back to the first metric when configured to', () => {
const result = buildSortMetricOrderby({
metrics: ['sum__num', 'avg__num'],
timeseriesLimitMetric: undefined,
fallbackToFirstMetric: true,
});
expect(result.metrics).toEqual(['sum__num', 'avg__num']);
expect(result.orderby).toEqual([['sum__num', true]]);
});
test('appends the sort metric when it is not already selected', () => {
const result = buildSortMetricOrderby({
metrics: ['sum__num'],
timeseriesLimitMetric: 'count',
});
expect(result.metrics).toEqual(['sum__num', 'count']);
});
test('does not duplicate the sort metric when already selected', () => {
const result = buildSortMetricOrderby({
metrics: ['sum__num', 'count'],
timeseriesLimitMetric: 'count',
});
expect(result.metrics).toEqual(['sum__num', 'count']);
});
test('unconditional ordering (orderOnlyWhenDesc: false) always orders, flipping direction', () => {
const ascending = buildSortMetricOrderby({
metrics: ['sum__num'],
timeseriesLimitMetric: 'count',
order_desc: false,
});
expect(ascending.orderby).toEqual([['count', true]]);
const descending = buildSortMetricOrderby({
metrics: ['sum__num'],
timeseriesLimitMetric: 'count',
order_desc: true,
});
expect(descending.orderby).toEqual([['count', false]]);
});
test('gated ordering (orderOnlyWhenDesc: true) only orders when order_desc is set', () => {
const withoutDesc = buildSortMetricOrderby({
metrics: ['sum__num'],
timeseriesLimitMetric: 'count',
orderOnlyWhenDesc: true,
});
expect(withoutDesc.metrics).toEqual(['sum__num', 'count']);
expect(withoutDesc.orderby).toEqual([]);
const withDesc = buildSortMetricOrderby({
metrics: ['sum__num'],
timeseriesLimitMetric: 'count',
order_desc: true,
orderOnlyWhenDesc: true,
});
expect(withDesc.orderby).toEqual([['count', false]]);
});
test('resolves a multi-value timeseriesLimitMetric to its first entry', () => {
const result = buildSortMetricOrderby({
metrics: ['sum__num'],
timeseriesLimitMetric: ['count', 'avg__num'],
});
expect(result.metrics).toEqual(['sum__num', 'count']);
expect(result.orderby).toEqual([['count', true]]);
});

View File

@@ -104,24 +104,15 @@ export default class ChartClient {
const buildQueryRegistry = getChartBuildQueryRegistry();
if (metaDataRegistry.has(visType)) {
const { useLegacyApi } = metaDataRegistry.get(visType)!;
const buildQuery =
(await buildQueryRegistry.get(visType)) ?? (() => formData);
const requestConfig: RequestConfig = useLegacyApi
? {
endpoint: '/explore_json/',
postPayload: {
form_data: buildQuery(formData),
},
...options,
}
: {
endpoint: '/api/v1/chart/data',
jsonPayload: {
query_context: buildQuery(formData),
},
...options,
};
const requestConfig: RequestConfig = {
endpoint: '/api/v1/chart/data',
jsonPayload: {
query_context: buildQuery(formData),
},
...options,
};
return this.client
.post(requestConfig)

View File

@@ -21,7 +21,6 @@ import { render, waitFor, configure, act } from '@testing-library/react';
import '@testing-library/jest-dom';
import StatefulChart from './StatefulChart';
import getChartControlPanelRegistry from '../registries/ChartControlPanelRegistrySingleton';
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
// Configure testing library to use data-test attribute
@@ -29,7 +28,6 @@ configure({ testIdAttribute: 'data-test' });
// Mock the registries
jest.mock('../registries/ChartControlPanelRegistrySingleton');
jest.mock('../registries/ChartMetadataRegistrySingleton');
jest.mock('../registries/ChartBuildQueryRegistrySingleton');
jest.mock('../clients/ChartClient');
@@ -67,12 +65,6 @@ beforeEach(() => {
jest.clearAllMocks();
// Setup default registry mocks
jest.mocked(getChartMetadataRegistry).mockReturnValue({
get: jest.fn().mockReturnValue({
useLegacyApi: false,
}),
} as unknown as ReturnType<typeof getChartMetadataRegistry>);
jest.mocked(getChartBuildQueryRegistry).mockReturnValue({
get: jest.fn().mockResolvedValue(null),
} as unknown as ReturnType<typeof getChartBuildQueryRegistry>);
@@ -746,11 +738,10 @@ test('resolves async (202) responses via the injected handleAsyncChartData hook'
await waitFor(() => {
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
});
// Delegates the raw response + job metadata (and useLegacyApi + abort signal)
// Delegates the raw response + job metadata (and abort signal)
expect(handleAsyncChartData).toHaveBeenCalledWith(
{ status: 202 },
asyncJob,
false,
expect.any(AbortSignal),
);
// Chart renders once the async data resolves
@@ -798,43 +789,6 @@ test('renders synchronous (200) responses that include a response object', async
expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
});
test('wraps the legacy async body as { result: [body] } for the async handler', async () => {
const legacyBody = { job_id: 'j1', channel_id: 'c1', status: 'running' };
mockChartClient.client.post.mockResolvedValue({
response: { status: 202 } as Response,
json: legacyBody,
});
// Force the legacy API path for this viz type
jest.mocked(getChartMetadataRegistry).mockReturnValue({
get: jest.fn().mockReturnValue({ useLegacyApi: true }),
} as unknown as ReturnType<typeof getChartMetadataRegistry>);
const handleAsyncChartData = jest
.fn()
.mockResolvedValue([{ data: 'legacy result' }]);
const { getByTestId } = render(
<StatefulChart
formData={mockFormData}
chartType="test_chart"
hooks={{ handleAsyncChartData }}
/>,
);
await waitFor(() => {
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
});
// Legacy body must be wrapped to match the V1 response signature
expect(handleAsyncChartData).toHaveBeenCalledWith(
{ status: 202 },
{ result: [legacyBody] },
true,
expect.any(AbortSignal),
);
await waitFor(() => {
expect(getByTestId('super-chart')).toBeInTheDocument();
});
});
test('does not apply a superseded async response over a newer one', async () => {
mockChartClient.client.post.mockResolvedValue({
response: { status: 202 } as Response,
@@ -1024,7 +978,7 @@ test('passes an abort signal to the async handler and aborts it on unmount', asy
response: { status: 202 } as Response,
json: { job_id: 'j', channel_id: 'c' },
});
// Typed with a rest param so mock.calls is indexable (the 4th arg is the signal)
// Typed with a rest param so mock.calls is indexable (the 3rd arg is the signal)
const handleAsyncChartData = jest.fn(
(..._args: unknown[]) => new Promise<never>(() => {}), // never resolves
);
@@ -1040,7 +994,7 @@ test('passes an abort signal to the async handler and aborts it on unmount', asy
await waitFor(() => {
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
});
const signal = handleAsyncChartData.mock.calls[0][3] as AbortSignal;
const signal = handleAsyncChartData.mock.calls[0][2] as AbortSignal;
expect(signal).toBeInstanceOf(AbortSignal);
expect(signal.aborted).toBe(false);

View File

@@ -34,7 +34,6 @@ import {
import { Loading } from '../../components/Loading';
import ChartClient from '../clients/ChartClient';
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
import getChartControlPanelRegistry from '../registries/ChartControlPanelRegistrySingleton';
import SuperChart from './SuperChart';
@@ -281,9 +280,6 @@ export default function StatefulChart(props: StatefulChartProps) {
}
finalFormData.viz_type = vizType;
// Get chart metadata
const { useLegacyApi } = getChartMetadataRegistry().get(vizType) || {};
// Build query using the chart's buildQuery function
const buildQuery = await getChartBuildQueryRegistry().get(vizType);
let queryContext;
@@ -295,31 +291,20 @@ export default function StatefulChart(props: StatefulChartProps) {
queryContext = buildQueryContext(finalFormData);
}
// Ensure query_context is properly formatted for new API
if (!useLegacyApi && !queryContext.queries) {
// Ensure query_context is properly formatted for the API
if (!queryContext.queries) {
queryContext = { queries: [queryContext] };
}
const endpoint = useLegacyApi ? '/explore_json/' : '/api/v1/chart/data';
const requestConfig: RequestConfig = {
endpoint,
endpoint: '/api/v1/chart/data',
signal: controller.signal,
...(timeout && { timeout: timeout * 1000 }),
};
if (useLegacyApi) {
requestConfig.postPayload = {
form_data: {
...finalFormData,
...(force && { force: true }),
},
};
} else {
requestConfig.jsonPayload = {
jsonPayload: {
...queryContext,
...(force && { force: true }),
};
}
},
};
const clientResponse =
await chartClientRef.current!.client.post(requestConfig);
@@ -347,18 +332,10 @@ export default function StatefulChart(props: StatefulChartProps) {
'the async handler or disable GLOBAL_ASYNC_QUERIES for this chart.',
);
}
// The async handler (handleChartDataResponse) expects the V1 chart data
// response signature. The legacy endpoint returns a flat body, so wrap
// it as { result: [body] } exactly like legacyChartDataRequest does for
// the standard chart path; the V1 body is already correctly shaped.
const asyncPayload = useLegacyApi
? ({ result: [clientResponse.json] } as JsonObject)
: (clientResponse.json as JsonObject);
responseData = ensureIsArray(
await hooks.handleAsyncChartData(
rawResponse,
asyncPayload,
useLegacyApi,
clientResponse.json as JsonObject,
controller.signal,
),
);
@@ -374,10 +351,8 @@ export default function StatefulChart(props: StatefulChartProps) {
: [clientResponse.json]
) as JsonObject[];
// Handle the nested result structure from the new API
responseData = (
!useLegacyApi && rows[0]?.result ? rows[0].result : rows
) as QueryData[];
// Handle the nested result structure from the API
responseData = (rows[0]?.result ? rows[0].result : rows) as QueryData[];
}
// Don't pair this request's data with newer props or fire a stale onLoad

View File

@@ -40,7 +40,6 @@ export interface ChartMetadataConfig {
supportedAnnotationTypes?: string[];
thumbnail: string;
thumbnailDark?: string;
useLegacyApi?: boolean;
behaviors?: Behavior[];
exampleGallery?: ExampleImage[];
tags?: string[];
@@ -75,8 +74,6 @@ export default class ChartMetadata {
thumbnailDark?: string;
useLegacyApi: boolean;
behaviors: Behavior[];
datasourceCount: number;
@@ -112,7 +109,6 @@ export default class ChartMetadata {
supportedAnnotationTypes = [],
thumbnail,
thumbnailDark,
useLegacyApi = false,
behaviors = [],
datasourceCount = 1,
enableNoResults = true,
@@ -144,7 +140,6 @@ export default class ChartMetadata {
this.supportedAnnotationTypes = supportedAnnotationTypes;
this.thumbnail = thumbnail;
this.thumbnailDark = thumbnailDark;
this.useLegacyApi = useLegacyApi;
this.behaviors = behaviors;
this.datasourceCount = datasourceCount;
this.enableNoResults = enableNoResults;

View File

@@ -75,7 +75,6 @@ type Hooks = {
handleAsyncChartData?: (
response: Response,
json: JsonObject,
useLegacyApi?: boolean,
signal?: AbortSignal,
) => Promise<QueryData[]> | QueryData[];
} & PlainObject;

View File

@@ -160,42 +160,6 @@ describe('ChartClient', () => {
datasource: '1__table',
}),
).rejects.toEqual(new Error('Unknown chart type: rainbow_3d_pie')));
test('fetches data from the legacy API if ChartMetadata has useLegacyApi=true,', () => {
// note legacy charts do not register a buildQuery function in the registry
getChartMetadataRegistry().registerValue(
'word_cloud_legacy',
new ChartMetadata({
name: 'Legacy Word Cloud',
thumbnail: '.png',
useLegacyApi: true,
}),
);
fetchMock.post('glob:*/api/v1/chart/data', () =>
Promise.reject(new Error('Unexpected all to v1 API')),
);
// post `Superset.route_base = ""`, the legacy endpoint
// collapsed from `/superset/explore_json/` to `/explore_json/`.
fetchMock.post('glob:*/explore_json/', {
field1: 'abc',
field2: 'def',
});
return expect(
chartClient.loadQueryData({
granularity: 'minute',
viz_type: 'word_cloud_legacy',
datasource: '1__table',
}),
).resolves.toEqual([
{
field1: 'abc',
field2: 'def',
},
]);
});
});
describe('.loadDatasource(datasourceKey, options)', () => {

View File

@@ -38,6 +38,14 @@
*
* NOTE: the embedded suite only runs when the embedded SDK bundle is built and
* INCLUDE_EMBEDDED=true (CI sets both). It is skipped otherwise.
*
* NOTE: the embedded project runs with admin storageState, and the session
* cookie the iframe ends up with is racy: the /embedded/<uuid> response
* rotates it to an anonymous session while the SDK's parallel csrf and
* guest-token fetches rewrite the admin one, so chart data requests are
* evaluated as either admin or guest depending on which response lands last.
* Fixture charts must therefore hold up under GUEST evaluation — see the
* query_context note below — or the suite only passes when admin wins.
*/
import { test, expect, Browser, BrowserContext, Page } from '@playwright/test';
import { createServer, IncomingMessage, ServerResponse, Server } from 'http';
@@ -182,12 +190,43 @@ test.describe('Embedded Pivot Table collapse state (#33406)', () => {
row_limit: 1000,
order_desc: true,
};
// Charts saved through Explore always persist a query_context alongside
// params. Store one here too: guest (embedded) requests are validated
// against the stored chart, and a params-only chart makes the guest
// payload look tampered (its query `columns` aren't found on the chart),
// failing every chart data request with a 403.
const queryContext = {
datasource: { id: datasetId, type: 'table' },
force: false,
queries: [
{
filters: [],
extras: { having: '', where: '' },
applied_time_extras: {},
columns: ['state', 'name'],
metrics: ['count'],
orderby: [['count', false]],
annotation_layers: [],
row_limit: 1000,
series_limit: 0,
order_desc: true,
url_params: {},
custom_params: {},
custom_form_data: {},
},
],
form_data: params,
result_format: 'json',
result_type: 'full',
};
const chartResp = await apiPost(setupPage, 'api/v1/chart/', {
slice_name: `pivot_collapse_repro_${Date.now()}`,
viz_type: 'pivot_table_v2',
datasource_id: datasetId,
datasource_type: 'table',
params: JSON.stringify(params),
query_context: JSON.stringify(queryContext),
query_context_generation: true,
});
chartId = (await chartResp.json()).id;

View File

@@ -1,143 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable react/no-array-index-key */
import { memo } from 'react';
import { styled } from '@apache-superset/core/theme';
import TTestTable, { DataEntry } from './TTestTable';
interface PairedTTestProps {
alpha?: number;
className?: string;
data: Record<string, DataEntry[]>;
groups: string[];
liftValPrec?: number;
metrics: string[];
pValPrec?: number;
}
const StyledDiv = styled.div`
${({ theme }) => `
.superset-legacy-chart-paired-t-test .scrollbar-container {
overflow: auto;
}
.paired-ttest-table .scrollbar-content {
padding-left: ${theme.sizeUnit}px;
padding-right: ${theme.sizeUnit}px;
margin-bottom: 0;
}
.paired-ttest-table table {
margin-bottom: 0;
}
.paired-ttest-table h1 {
margin-left: ${theme.sizeUnit}px;
}
.reactable-data tr {
font-feature-settings: 'tnum' 1;
}
.reactable-data tr,
.reactable-header-sortable {
-webkit-transition: ease-in-out 0.1s;
transition: ease-in-out 0.1s;
}
.reactable-data tr:hover {
background-color: ${theme.colorFillTertiary};
}
.reactable-data tr .false {
color: ${theme.colorError};
}
.reactable-data tr .true {
color: ${theme.colorSuccess};
}
.reactable-data tr .control {
color: ${theme.colorPrimary};
}
.reactable-data tr .invalid {
color: ${theme.colorWarning};
}
.reactable-data .control td {
background-color: ${theme.colorFillTertiary};
}
.reactable-header-sortable:hover,
.reactable-header-sortable:focus,
.reactable-header-sort-asc,
.reactable-header-sort-desc {
background-color: ${theme.colorFillTertiary};
position: relative;
}
.reactable-header-sort-asc:after {
content: '\\25bc';
position: absolute;
right: ${theme.sizeUnit * 3}px;
}
.reactable-header-sort-desc:after {
content: '\\25b2';
position: absolute;
right: ${theme.sizeUnit * 3}px;
}
`}
`;
function PairedTTest({
alpha = 0.05,
className = '',
data,
groups,
liftValPrec = 4,
metrics,
pValPrec = 6,
}: PairedTTestProps) {
return (
<StyledDiv>
<div className={`superset-legacy-chart-paired-t-test ${className}`}>
<div className="paired-ttest-table">
<div className="scrollbar-content">
{metrics.map((metric, i) => (
<TTestTable
key={i}
metric={metric}
groups={groups}
data={data[metric]}
alpha={alpha}
pValPrec={Math.min(pValPrec, 32)}
liftValPrec={Math.min(liftValPrec, 32)}
/>
))}
</div>
</div>
</div>
</StyledDiv>
);
}
// memo preserves the shallow-prop render bailout of the PureComponent original
export default memo(PairedTTest);

View File

@@ -1,61 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.20.0](https://github.com/apache/superset/compare/v2021.41.0...v0.20.0) (2024-09-09)
### Bug Fixes
- **Dashboard:** Color inconsistency on refreshes and conflicts ([#27439](https://github.com/apache/superset/issues/27439)) ([313ee59](https://github.com/apache/superset/commit/313ee596f5435894f857d72be7269d5070c8c964))
- **explore:** make SORT-Descending visible if Sort-by has value ([#17726](https://github.com/apache/superset/issues/17726)) ([d5768ab](https://github.com/apache/superset/commit/d5768ab649a70fd4f541ad4982498f622160b220))
### Features
- apply standardized form data to tier 2 charts ([#20530](https://github.com/apache/superset/issues/20530)) ([de524bc](https://github.com/apache/superset/commit/de524bc59f011fd361dcdb7d35c2cb51f7eba442))
- **explore:** improve UI in the control panel ([#19748](https://github.com/apache/superset/issues/19748)) ([e3a54aa](https://github.com/apache/superset/commit/e3a54aa3c15bdd0c970aa73f898288a408205c97))
- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11))
- update time comparison choices (again) ([#17968](https://github.com/apache/superset/issues/17968)) ([05d9cde](https://github.com/apache/superset/commit/05d9cde203b99f8c63106446f0be58668cc9f0c9))
- update time comparison choices (again) ([#22458](https://github.com/apache/superset/issues/22458)) ([9e81c3a](https://github.com/apache/superset/commit/9e81c3a1192a18226d505178d16e1e395917a719))
# [0.19.0](https://github.com/apache/superset/compare/v2021.41.0...v0.19.0) (2024-09-07)
### Bug Fixes
- **Dashboard:** Color inconsistency on refreshes and conflicts ([#27439](https://github.com/apache/superset/issues/27439)) ([313ee59](https://github.com/apache/superset/commit/313ee596f5435894f857d72be7269d5070c8c964))
- **explore:** make SORT-Descending visible if Sort-by has value ([#17726](https://github.com/apache/superset/issues/17726)) ([d5768ab](https://github.com/apache/superset/commit/d5768ab649a70fd4f541ad4982498f622160b220))
### Features
- apply standardized form data to tier 2 charts ([#20530](https://github.com/apache/superset/issues/20530)) ([de524bc](https://github.com/apache/superset/commit/de524bc59f011fd361dcdb7d35c2cb51f7eba442))
- **explore:** improve UI in the control panel ([#19748](https://github.com/apache/superset/issues/19748)) ([e3a54aa](https://github.com/apache/superset/commit/e3a54aa3c15bdd0c970aa73f898288a408205c97))
- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11))
- update time comparison choices (again) ([#17968](https://github.com/apache/superset/issues/17968)) ([05d9cde](https://github.com/apache/superset/commit/05d9cde203b99f8c63106446f0be58668cc9f0c9))
- update time comparison choices (again) ([#22458](https://github.com/apache/superset/issues/22458)) ([9e81c3a](https://github.com/apache/superset/commit/9e81c3a1192a18226d505178d16e1e395917a719))
# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30)
**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-rose
## [0.17.61](https://github.com/apache-superset/superset-ui/compare/v0.17.60...v0.17.61) (2021-07-02)
**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-rose

View File

@@ -1,52 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
## @superset-ui/legacy-plugin-chart-rose
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-rose.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-rose)
[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Flegacy-plugin-chart-rose?style=flat)](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-rose)
This plugin provides Nightingale Rose Diagram for Superset.
### Usage
Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to
lookup this chart throughout the app.
```js
import RoseChartPlugin from '@superset-ui/legacy-plugin-chart-rose';
new RoseChartPlugin().configure({ key: 'rose' }).register();
```
Then use it via `SuperChart`. See
[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-rose)
for more details.
```js
<SuperChart
chartType="rose"
width={600}
height={600}
formData={...}
queriesData={[{
data: {...},
}]}
/>
```

View File

@@ -1,40 +0,0 @@
{
"name": "@superset-ui/legacy-plugin-chart-rose",
"version": "0.20.3",
"description": "Superset Legacy Chart - Nightingale Rose Diagram",
"keywords": [
"superset"
],
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-rose#readme",
"bugs": {
"url": "https://github.com/apache/superset/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/apache/superset.git",
"directory": "superset-frontend/plugins/legacy-plugin-chart-rose"
},
"license": "Apache-2.0",
"author": "Superset",
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"dependencies": {
"d3": "^3.5.17",
"nvd3-fork": "^2.0.5",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@apache-superset/core": "*",
"react": "^18.3.0"
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -1,87 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { reactify } from '@superset-ui/core';
import { styled, css } from '@apache-superset/core/theme';
import { Global } from '@emotion/react';
import Component from './Rose';
// Type-erase the render function to allow flexible prop spreading in the wrapper.
// The Rose render function has typed props, but the wrapper passes props via spread
// which TypeScript cannot verify at compile time. Props are validated at runtime.
const ReactComponent = reactify(
Component as unknown as (
container: HTMLDivElement,
props: Record<string, unknown>,
) => void,
);
interface RoseWrapperProps {
className?: string;
[key: string]: unknown;
}
const Rose = ({ className, ...otherProps }: RoseWrapperProps) => (
<div className={className}>
<Global
styles={theme => css`
.tooltip {
line-height: 1;
padding: ${theme.sizeUnit * 3}px;
background: ${theme.colorBgElevated};
color: ${theme.colorText};
border-radius: 4px;
pointer-events: none;
z-index: 1000;
font-size: ${theme.fontSizeSM}px;
}
`}
/>
<ReactComponent {...otherProps} />
</div>
);
export default styled(Rose)`
${({ theme }) => `
.superset-legacy-chart-rose path {
transition: fill-opacity 180ms linear;
stroke: ${theme.colorBorder};
stroke-width: 1px;
stroke-opacity: 1;
fill-opacity: 0.75;
}
.superset-legacy-chart-rose text {
font-size: ${theme.fontSizeSM}px;
font-family: ${theme.fontFamily};
pointer-events: none;
}
.superset-legacy-chart-rose .clickable path {
cursor: pointer;
}
.superset-legacy-chart-rose .hover path {
fill-opacity: 1;
}
.nv-legend .nv-series {
cursor: pointer;
}
`}
`;

View File

@@ -1,697 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @ts-nocheck
/* oxlint-disable no-use-before-define: ["error", { "functions": false }] */
/* eslint-disable no-restricted-syntax */
/* eslint-disable react/sort-prop-types */
import d3 from 'd3';
import PropTypes from 'prop-types';
import nv from 'nvd3-fork';
import {
getTimeFormatter,
getNumberFormatter,
CategoricalColorNamespace,
sanitizeHtml,
} from '@superset-ui/core';
interface RoseDataEntry {
key: string[];
name: string;
time: number;
value: number;
id: number;
}
interface RoseData {
[timestamp: string]: RoseDataEntry[];
}
interface ArcDatum {
startAngle: number;
endAngle: number;
innerRadius: number;
outerRadius: number;
name?: string;
arcId?: number;
val?: number;
time?: number;
percent?: number;
}
interface RoseProps {
data: RoseData;
width: number;
height: number;
colorScheme: string;
dateTimeFormat: string;
numberFormat: string;
useRichTooltip: boolean;
useAreaProportions: boolean;
sliceId: number;
}
const propTypes = {
// Data is an object hashed by numeric value, perhaps timestamp
data: PropTypes.objectOf(
PropTypes.arrayOf(
PropTypes.shape({
key: PropTypes.arrayOf(PropTypes.string),
name: PropTypes.arrayOf(PropTypes.string),
time: PropTypes.number,
value: PropTypes.number,
}),
),
),
width: PropTypes.number,
height: PropTypes.number,
dateTimeFormat: PropTypes.string,
numberFormat: PropTypes.string,
useRichTooltip: PropTypes.bool,
useAreaProportions: PropTypes.bool,
colorScheme: PropTypes.string,
};
function copyArc(d: ArcDatum): ArcDatum {
return {
startAngle: d.startAngle,
endAngle: d.endAngle,
innerRadius: d.innerRadius,
outerRadius: d.outerRadius,
};
}
function sortValues(a: RoseDataEntry, b: RoseDataEntry): number {
if (a.value === b.value) {
return a.name > b.name ? 1 : -1;
}
return b.value - a.value;
}
function Rose(element: HTMLElement, props: RoseProps): void {
const {
data,
width,
height,
colorScheme,
dateTimeFormat,
numberFormat,
useRichTooltip,
useAreaProportions,
sliceId,
} = props;
const div = d3.select(element);
div.classed('superset-legacy-chart-rose', true);
const datum = data;
const times = Object.keys(datum)
.map(t => parseInt(t, 10))
.sort((a, b) => a - b);
const numGrains = times.length;
const numGroups = datum[times[0]].length;
const format = getNumberFormatter(numberFormat);
const timeFormat = getTimeFormatter(dateTimeFormat);
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
d3.select('.nvtooltip').remove();
div.selectAll('*').remove();
const arc = d3.svg.arc();
const legend = nv.models.legend();
const tooltip = nv.models.tooltip();
const state = { disabled: datum[times[0]].map(() => false) };
const svg = div.append('svg').attr('width', width).attr('height', height);
const g = svg.append('g').attr('class', 'rose').append('g');
const legendWrap = g.append('g').attr('class', 'legendWrap');
function legendData(adatum: RoseData) {
return adatum[times[0]].map((v: RoseDataEntry, i: number) => ({
disabled: state.disabled[i],
// Keep the raw name as `key` so it matches the value used for arc
// fills (colorFn is called with d.name on arcs and d.key on the
// legend). nvd3-fork's legend renders `key` via .text(), so the
// raw value is escaped at the DOM sink.
key: v.name,
}));
}
function tooltipData(d: ArcDatum, i: number, adatum: RoseData) {
const timeIndex = Math.floor(d.arcId / numGroups);
// nvd3-fork's nv.models.tooltip renders the `key` strings via .html(),
// so any HTML in user-controlled column values would execute. Pass the
// keys through sanitizeHtml to strip dangerous markup while preserving
// legitimate text content.
const series = useRichTooltip
? adatum[times[timeIndex]]
.filter(v => !state.disabled[v.id % numGroups])
.map(v => ({
key: sanitizeHtml(v.name),
value: v.value,
color: colorFn(v.name, sliceId),
highlight: v.id === d.arcId,
}))
: [
{
key: sanitizeHtml(d.name),
value: d.val,
color: colorFn(d.name, sliceId),
},
];
return {
key: 'Date',
value: d.time,
series,
};
}
legend.width(width).color(d => colorFn(d.key, sliceId));
legendWrap.datum(legendData(datum)).call(legend);
tooltip.headerFormatter(timeFormat).valueFormatter(format);
tooltip.classes('tooltip');
// Compute max radius, which the largest value will occupy
const roseHeight = height - legend.height();
const margin = { top: legend.height() };
const edgeMargin = 35; // space between outermost radius and slice edge
const maxRadius = Math.min(width, roseHeight) / 2 - edgeMargin;
const labelThreshold = 0.05;
const gro = 8; // mouseover radius growth in pixels
const mini = 0.075;
const centerTranslate = `translate(${width / 2},${
roseHeight / 2 + margin.top
})`;
const roseWrap = g
.append('g')
.attr('transform', centerTranslate)
.attr('class', 'roseWrap');
const labelsWrap = g
.append('g')
.attr('transform', centerTranslate)
.attr('class', 'labelsWrap');
const groupLabelsWrap = g
.append('g')
.attr('transform', centerTranslate)
.attr('class', 'groupLabelsWrap');
// Compute inner and outer angles for each data point
function computeArcStates(adatum: RoseData) {
// Find the max sum of values across all time
let maxSum = 0;
let grain = 0;
const sums = [];
for (const t of times) {
const sum = datum[t].reduce(
(a, v, i) => a + (state.disabled[i] ? 0 : v.value),
0,
);
maxSum = sum > maxSum ? sum : maxSum;
sums[grain] = sum;
grain += 1;
}
// Compute angle occupied by each time grain
const dtheta = (Math.PI * 2) / numGrains;
const angles = [];
for (let i = 0; i <= numGrains; i += 1) {
angles.push(dtheta * i - Math.PI / 2);
}
// Compute proportion
const P = maxRadius / maxSum;
const Q = P * maxRadius;
const computeOuterRadius = (value: number, innerRadius: number): number =>
useAreaProportions
? Math.sqrt(Q * value + innerRadius * innerRadius)
: P * value + innerRadius;
const arcSt = {
data: [],
extend: {},
push: {},
pieStart: {},
pie: {},
pieOver: {},
mini: {},
labels: [],
groupLabels: [],
};
let arcId = 0;
for (let i = 0; i < numGrains; i += 1) {
const t = times[i];
const startAngle = angles[i];
const endAngle = angles[i + 1];
const G = (2 * Math.PI) / sums[i];
let innerRadius = 0;
let outerRadius;
let pieStartAngle = 0;
let pieEndAngle;
for (const v of adatum[t]) {
const val = state.disabled[arcId % numGroups] ? 0 : v.value;
const { name, time } = v;
v.id = arcId;
outerRadius = computeOuterRadius(val, innerRadius);
arcSt.data.push({
startAngle,
endAngle,
innerRadius,
outerRadius,
name,
arcId,
val,
time,
});
arcSt.extend[arcId] = {
startAngle,
endAngle,
innerRadius,
name,
outerRadius: outerRadius + gro,
};
arcSt.push[arcId] = {
startAngle,
endAngle,
innerRadius: innerRadius + gro,
outerRadius: outerRadius + gro,
};
arcSt.pieStart[arcId] = {
startAngle,
endAngle,
innerRadius: mini * maxRadius,
outerRadius: maxRadius,
};
arcSt.mini[arcId] = {
startAngle,
endAngle,
innerRadius: innerRadius * mini,
outerRadius: outerRadius * mini,
};
arcId += 1;
innerRadius = outerRadius;
}
const labelArc = { ...arcSt.data[i * numGroups] };
labelArc.outerRadius = maxRadius + 20;
labelArc.innerRadius = maxRadius + 15;
arcSt.labels.push(labelArc);
for (const v of adatum[t].concat().sort(sortValues)) {
const val = state.disabled[v.id % numGroups] ? 0 : v.value;
pieEndAngle = G * val + pieStartAngle;
arcSt.pie[v.id] = {
startAngle: pieStartAngle,
endAngle: pieEndAngle,
innerRadius: maxRadius * mini,
outerRadius: maxRadius,
percent: v.value / sums[i],
};
arcSt.pieOver[v.id] = {
startAngle: pieStartAngle,
endAngle: pieEndAngle,
innerRadius: maxRadius * mini,
outerRadius: maxRadius + gro,
};
pieStartAngle = pieEndAngle;
}
}
arcSt.groupLabels = arcSt.data.slice(0, numGroups);
return arcSt;
}
let arcSt = computeArcStates(datum);
function tween(target: ArcDatum, resFunc: (d: ArcDatum) => string) {
return function doTween(d: ArcDatum) {
const interpolate = d3.interpolate(copyArc(d), copyArc(target));
return (t: number) => resFunc(Object.assign(d, interpolate(t)));
};
}
function arcTween(target: ArcDatum) {
return tween(target, (d: ArcDatum) => arc(d));
}
function translateTween(target: ArcDatum) {
return tween(target, (d: ArcDatum) => `translate(${arc.centroid(d)})`);
}
// Grab the ID range of segments stand between
// this segment and the edge of the circle
const segmentsToEdgeCache: Record<number, [number, number]> = {};
function getSegmentsToEdge(arcId: number): [number, number] {
if (segmentsToEdgeCache[arcId]) {
return segmentsToEdgeCache[arcId];
}
const timeIndex = Math.floor(arcId / numGroups);
segmentsToEdgeCache[arcId] = [arcId + 1, numGroups * (timeIndex + 1) - 1];
return segmentsToEdgeCache[arcId];
}
// Get the IDs of all segments in a timeIndex
const segmentsInTimeCache: Record<number, [number, number]> = {};
function getSegmentsInTime(arcId: number): [number, number] {
if (segmentsInTimeCache[arcId]) {
return segmentsInTimeCache[arcId];
}
const timeIndex = Math.floor(arcId / numGroups);
segmentsInTimeCache[arcId] = [
timeIndex * numGroups,
(timeIndex + 1) * numGroups - 1,
];
return segmentsInTimeCache[arcId];
}
let clickId = -1;
let inTransition = false;
const ae = roseWrap
.selectAll('g')
.data(JSON.parse(JSON.stringify(arcSt.data))) // deep copy data state
.enter()
.append('g')
.attr('class', 'segment')
.classed('clickable', true);
const labels = labelsWrap
.selectAll('g')
.data(JSON.parse(JSON.stringify(arcSt.labels)))
.enter()
.append('g')
.attr('class', 'roseLabel')
.attr('transform', d => `translate(${arc.centroid(d)})`);
labels
.append('text')
.style('text-anchor', 'middle')
.style('fill', '#000')
.text(d => timeFormat(d.time));
const groupLabels = groupLabelsWrap
.selectAll('g')
.data(JSON.parse(JSON.stringify(arcSt.groupLabels)))
.enter()
.append('g');
groupLabels
.style('opacity', 0)
.attr('class', 'roseGroupLabels')
.append('text')
.style('text-anchor', 'middle')
.style('fill', '#000')
.text(d => d.name);
const arcs = ae
.append('path')
.attr('class', 'arc')
.attr('fill', d => colorFn(d.name, sliceId))
.attr('d', arc);
function mousemove(): void {
tooltip();
}
function mouseover(this: Element, b: ArcDatum, i: number): void {
tooltip.data(tooltipData(b, i, datum)).hidden(false);
const $this = d3.select(this);
$this.classed('hover', true);
if (clickId < 0 && !inTransition) {
$this
.select('path')
.interrupt()
.transition()
.duration(180)
.attrTween('d', arcTween(arcSt.extend[i]));
const edge = getSegmentsToEdge(i);
arcs
.filter(d => edge[0] <= d.arcId && d.arcId <= edge[1])
.interrupt()
.transition()
.duration(180)
.attrTween('d', d => arcTween(arcSt.push[d.arcId])(d));
} else if (!inTransition) {
const segments = getSegmentsInTime(clickId);
if (segments[0] <= b.arcId && b.arcId <= segments[1]) {
$this
.select('path')
.interrupt()
.transition()
.duration(180)
.attrTween('d', arcTween(arcSt.pieOver[i]));
}
}
}
function mouseout(this: Element, b: ArcDatum, i: number): void {
tooltip.hidden(true);
const $this = d3.select(this);
$this.classed('hover', false);
if (clickId < 0 && !inTransition) {
$this
.select('path')
.interrupt()
.transition()
.duration(180)
.attrTween('d', arcTween(arcSt.data[i]));
const edge = getSegmentsToEdge(i);
arcs
.filter(d => edge[0] <= d.arcId && d.arcId <= edge[1])
.interrupt()
.transition()
.duration(180)
.attrTween('d', d => arcTween(arcSt.data[d.arcId])(d));
} else if (!inTransition) {
const segments = getSegmentsInTime(clickId);
if (segments[0] <= b.arcId && b.arcId <= segments[1]) {
$this
.select('path')
.interrupt()
.transition()
.duration(180)
.attrTween('d', arcTween(arcSt.pie[i]));
}
}
}
function click(b: ArcDatum, i: number): void {
if (inTransition) {
return;
}
const delay = d3.event.altKey ? 3750 : 375;
const segments = getSegmentsInTime(i);
if (clickId < 0) {
inTransition = true;
clickId = i;
labels
.interrupt()
.transition()
.duration(delay)
.attrTween('transform', d =>
translateTween({
outerRadius: 0,
innerRadius: 0,
startAngle: d.startAngle,
endAngle: d.endAngle,
})(d),
)
.style('opacity', 0);
groupLabels
.attr(
'transform',
`translate(${arc.centroid({
outerRadius: maxRadius + 20,
innerRadius: maxRadius + 15,
startAngle: arcSt.data[i].startAngle,
endAngle: arcSt.data[i].endAngle,
})})`,
)
.interrupt()
.transition()
.delay(delay)
.duration(delay)
.attrTween('transform', d =>
translateTween({
outerRadius: maxRadius + 20,
innerRadius: maxRadius + 15,
startAngle: arcSt.pie[segments[0] + d.arcId].startAngle,
endAngle: arcSt.pie[segments[0] + d.arcId].endAngle,
})(d),
)
.style('opacity', d =>
state.disabled[d.arcId] ||
arcSt.pie[segments[0] + d.arcId].percent < labelThreshold
? 0
: 1,
);
ae.classed(
'clickable',
d => segments[0] > d.arcId || d.arcId > segments[1],
);
arcs
.filter(d => segments[0] <= d.arcId && d.arcId <= segments[1])
.interrupt()
.transition()
.duration(delay)
.attrTween('d', d => arcTween(arcSt.pieStart[d.arcId])(d))
.transition()
.duration(delay)
.attrTween('d', d => arcTween(arcSt.pie[d.arcId])(d))
.each('end', () => {
inTransition = false;
});
arcs
.filter(d => segments[0] > d.arcId || d.arcId > segments[1])
.interrupt()
.transition()
.duration(delay)
.attrTween('d', d => arcTween(arcSt.mini[d.arcId])(d));
} else if (clickId < segments[0] || segments[1] < clickId) {
inTransition = true;
const clickSegments = getSegmentsInTime(clickId);
labels
.interrupt()
.transition()
.delay(delay)
.duration(delay)
.attrTween('transform', d =>
translateTween(arcSt.labels[d.arcId / numGroups])(d),
)
.style('opacity', 1);
groupLabels
.interrupt()
.transition()
.duration(delay)
.attrTween(
'transform',
translateTween({
outerRadius: maxRadius + 20,
innerRadius: maxRadius + 15,
startAngle: arcSt.data[clickId].startAngle,
endAngle: arcSt.data[clickId].endAngle,
}),
)
.style('opacity', 0);
ae.classed('clickable', true);
arcs
.filter(d => clickSegments[0] <= d.arcId && d.arcId <= clickSegments[1])
.interrupt()
.transition()
.duration(delay)
.attrTween('d', d => arcTween(arcSt.pieStart[d.arcId])(d))
.transition()
.duration(delay)
.attrTween('d', d => arcTween(arcSt.data[d.arcId])(d))
.each('end', () => {
clickId = -1;
inTransition = false;
});
arcs
.filter(d => clickSegments[0] > d.arcId || d.arcId > clickSegments[1])
.interrupt()
.transition()
.delay(delay)
.duration(delay)
.attrTween('d', d => arcTween(arcSt.data[d.arcId])(d));
}
}
ae.on('mouseover', mouseover)
.on('mouseout', mouseout)
.on('mousemove', mousemove)
.on('click', click);
function updateActive(): void {
const delay = d3.event.altKey ? 3000 : 300;
legendWrap.datum(legendData(datum)).call(legend);
const nArcSt = computeArcStates(datum);
inTransition = true;
if (clickId < 0) {
arcs
.style('opacity', 1)
.interrupt()
.transition()
.duration(delay)
.attrTween('d', d => arcTween(nArcSt.data[d.arcId])(d))
.each('end', () => {
inTransition = false;
arcSt = nArcSt;
})
.transition()
.duration(0)
.style('opacity', d => (state.disabled[d.arcId % numGroups] ? 0 : 1));
} else {
const segments = getSegmentsInTime(clickId);
arcs
.style('opacity', 1)
.interrupt()
.transition()
.duration(delay)
.attrTween('d', d =>
segments[0] <= d.arcId && d.arcId <= segments[1]
? arcTween(nArcSt.pie[d.arcId])(d)
: arcTween(nArcSt.mini[d.arcId])(d),
)
.each('end', () => {
inTransition = false;
arcSt = nArcSt;
})
.transition()
.duration(0)
.style('opacity', d => (state.disabled[d.arcId % numGroups] ? 0 : 1));
groupLabels
.interrupt()
.transition()
.duration(delay)
.attrTween('transform', d =>
translateTween({
outerRadius: maxRadius + 20,
innerRadius: maxRadius + 15,
startAngle: nArcSt.pie[segments[0] + d.arcId].startAngle,
endAngle: nArcSt.pie[segments[0] + d.arcId].endAngle,
})(d),
)
.style('opacity', d =>
state.disabled[d.arcId] ||
arcSt.pie[segments[0] + d.arcId].percent < labelThreshold
? 0
: 1,
);
}
}
legend.dispatch.on('stateChange', newState => {
if (state.disabled !== newState.disabled) {
state.disabled = newState.disabled;
updateActive();
}
});
}
Rose.displayName = 'Rose';
Rose.propTypes = propTypes;
export default Rose;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,95 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable no-magic-numbers, sort-keys */
import { SuperChart, VizType } from '@superset-ui/core';
import RoseChartPlugin from '@superset-ui/legacy-plugin-chart-rose';
import data from './data';
import { withResizableChartDemo } from '@storybook-shared';
new RoseChartPlugin().configure({ key: VizType.Rose }).register();
export default {
title: 'Legacy Chart Plugins/legacy-plugin-chart-rose',
decorators: [withResizableChartDemo],
args: {
colorScheme: 'd3Category10',
numberFormat: '.3s',
dateTimeFormat: '%Y-%m-%d',
richTooltip: true,
roseAreaProportion: false,
},
argTypes: {
colorScheme: {
control: 'select',
options: [
'supersetColors',
'd3Category10',
'bnbColors',
'googleCategory20c',
],
},
numberFormat: {
control: 'select',
options: ['SMART_NUMBER', '.2f', '.0%', '$,.2f', '.3s', ',d'],
},
dateTimeFormat: {
control: 'select',
options: ['%Y-%m-%d', '%Y-%m-%d %H:%M', '%b %d, %Y', '%d/%m/%Y'],
},
richTooltip: { control: 'boolean' },
roseAreaProportion: {
control: 'boolean',
description:
'When true, area is proportional to value; when false, radius is proportional',
},
},
};
export const Basic = ({
width,
height,
colorScheme,
numberFormat,
dateTimeFormat,
richTooltip,
roseAreaProportion,
}: {
width: number;
height: number;
colorScheme: string;
numberFormat: string;
dateTimeFormat: string;
richTooltip: boolean;
roseAreaProportion: boolean;
}) => (
<SuperChart
chartType={VizType.Rose}
width={width}
height={height}
queriesData={[{ data }]}
formData={{
color_scheme: colorScheme,
date_time_format: dateTimeFormat,
number_format: numberFormat,
rich_tooltip: richTooltip,
rose_area_proportion: roseAreaProportion,
}}
/>
);

View File

@@ -1,950 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable sort-keys, no-magic-numbers */
export default {
'-157766400000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: -157766400000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: -157766400000.0,
},
{
key: ['David'],
value: 6820.0,
name: ['David'],
time: -157766400000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: -157766400000.0,
},
{
key: ['Michael'],
value: 7835.0,
name: ['Michael'],
time: -157766400000.0,
},
],
'-126230400000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: -126230400000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: -126230400000.0,
},
{
key: ['David'],
value: 6757.0,
name: ['David'],
time: -126230400000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: -126230400000.0,
},
{
key: ['Michael'],
value: 7678.0,
name: ['Michael'],
time: -126230400000.0,
},
],
'-94694400000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: -94694400000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: -94694400000.0,
},
{
key: ['David'],
value: 6923.0,
name: ['David'],
time: -94694400000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: -94694400000.0,
},
{
key: ['Michael'],
value: 8132.0,
name: ['Michael'],
time: -94694400000.0,
},
],
'-63158400000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: -63158400000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: -63158400000.0,
},
{
key: ['David'],
value: 6490.0,
name: ['David'],
time: -63158400000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: -63158400000.0,
},
{
key: ['Michael'],
value: 8079.0,
name: ['Michael'],
time: -63158400000.0,
},
],
'-31536000000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: -31536000000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: -31536000000.0,
},
{
key: ['David'],
value: 6563.0,
name: ['David'],
time: -31536000000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: -31536000000.0,
},
{
key: ['Michael'],
value: 8245.0,
name: ['Michael'],
time: -31536000000.0,
},
],
'0': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 0.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 0.0,
},
{
key: ['David'],
value: 6367.0,
name: ['David'],
time: 0.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 0.0,
},
{
key: ['Michael'],
value: 8186.0,
name: ['Michael'],
time: 0.0,
},
],
'31536000000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 31536000000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 31536000000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 31536000000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 31536000000.0,
},
{
key: ['Michael'],
value: 6825.0,
name: ['Michael'],
time: 31536000000.0,
},
],
'63072000000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 63072000000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 63072000000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 63072000000.0,
},
{
key: ['Jennifer'],
value: 6066.0,
name: ['Jennifer'],
time: 63072000000.0,
},
{
key: ['Michael'],
value: 6337.0,
name: ['Michael'],
time: 63072000000.0,
},
],
'94694400000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 94694400000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 94694400000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 94694400000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 94694400000.0,
},
{
key: ['Michael'],
value: 5877.0,
name: ['Michael'],
time: 94694400000.0,
},
],
'126230400000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 126230400000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 126230400000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 126230400000.0,
},
{
key: ['Jennifer'],
value: 5672.0,
name: ['Jennifer'],
time: 126230400000.0,
},
{
key: ['Michael'],
value: 6058.0,
name: ['Michael'],
time: 126230400000.0,
},
],
'157766400000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 157766400000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 157766400000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 157766400000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 157766400000.0,
},
{
key: ['Michael'],
value: 6206.0,
name: ['Michael'],
time: 157766400000.0,
},
],
'189302400000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 189302400000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 189302400000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 189302400000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 189302400000.0,
},
{
key: ['Michael'],
value: 6178.0,
name: ['Michael'],
time: 189302400000.0,
},
],
'220924800000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 220924800000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 220924800000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 220924800000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 220924800000.0,
},
{
key: ['Michael'],
value: 6313.0,
name: ['Michael'],
time: 220924800000.0,
},
],
'252460800000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 252460800000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 252460800000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 252460800000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 252460800000.0,
},
{
key: ['Michael'],
value: 6489.0,
name: ['Michael'],
time: 252460800000.0,
},
],
'283996800000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 283996800000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 283996800000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 283996800000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 283996800000.0,
},
{
key: ['Michael'],
value: 6580.0,
name: ['Michael'],
time: 283996800000.0,
},
],
'315532800000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 315532800000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 315532800000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 315532800000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 315532800000.0,
},
{
key: ['Michael'],
value: 6896.0,
name: ['Michael'],
time: 315532800000.0,
},
],
'347155200000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 347155200000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 347155200000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 347155200000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 347155200000.0,
},
{
key: ['Michael'],
value: 7142.0,
name: ['Michael'],
time: 347155200000.0,
},
],
'378691200000000000': [
{
key: ['Christopher'],
value: 6093.0,
name: ['Christopher'],
time: 378691200000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 378691200000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 378691200000.0,
},
{
key: ['Jennifer'],
value: 5811.0,
name: ['Jennifer'],
time: 378691200000.0,
},
{
key: ['Michael'],
value: 7155.0,
name: ['Michael'],
time: 378691200000.0,
},
],
'410227200000000000': [
{
key: ['Christopher'],
value: 6375.0,
name: ['Christopher'],
time: 410227200000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 410227200000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 410227200000.0,
},
{
key: ['Jennifer'],
value: 5829.0,
name: ['Jennifer'],
time: 410227200000.0,
},
{
key: ['Michael'],
value: 7386.0,
name: ['Michael'],
time: 410227200000.0,
},
],
'441763200000000000': [
{
key: ['Christopher'],
value: 6509.0,
name: ['Christopher'],
time: 441763200000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 441763200000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 441763200000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 441763200000.0,
},
{
key: ['Michael'],
value: 7478.0,
name: ['Michael'],
time: 441763200000.0,
},
],
'473385600000000000': [
{
key: ['Christopher'],
value: 6744.0,
name: ['Christopher'],
time: 473385600000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 473385600000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 473385600000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 473385600000.0,
},
{
key: ['Michael'],
value: 7210.0,
name: ['Michael'],
time: 473385600000.0,
},
],
'504921600000000000': [
{
key: ['Christopher'],
value: 6497.0,
name: ['Christopher'],
time: 504921600000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 504921600000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 504921600000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 504921600000.0,
},
{
key: ['Michael'],
value: 7259.0,
name: ['Michael'],
time: 504921600000.0,
},
],
'536457600000000000': [
{
key: ['Christopher'],
value: 6549.0,
name: ['Christopher'],
time: 536457600000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 536457600000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 536457600000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 536457600000.0,
},
{
key: ['Michael'],
value: 7448.0,
name: ['Michael'],
time: 536457600000.0,
},
],
'567993600000000000': [
{
key: ['Christopher'],
value: 6496.0,
name: ['Christopher'],
time: 567993600000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 567993600000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 567993600000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 567993600000.0,
},
{
key: ['Michael'],
value: 7748.0,
name: ['Michael'],
time: 567993600000.0,
},
],
'599616000000000000': [
{
key: ['Christopher'],
value: 6526.0,
name: ['Christopher'],
time: 599616000000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 599616000000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 599616000000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 599616000000.0,
},
{
key: ['Michael'],
value: 7851.0,
name: ['Michael'],
time: 599616000000.0,
},
],
'631152000000000000': [
{
key: ['Christopher'],
value: 6641.0,
name: ['Christopher'],
time: 631152000000.0,
},
{
key: ['Daniel'],
value: 5760.0,
name: ['Daniel'],
time: 631152000000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 631152000000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 631152000000.0,
},
{
key: ['Michael'],
value: 8233.0,
name: ['Michael'],
time: 631152000000.0,
},
],
'662688000000000000': [
{
key: ['Christopher'],
value: 5784.0,
name: ['Christopher'],
time: 662688000000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 662688000000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 662688000000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 662688000000.0,
},
{
key: ['Michael'],
value: 7579.0,
name: ['Michael'],
time: 662688000000.0,
},
],
'694224000000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 694224000000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 694224000000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 694224000000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 694224000000.0,
},
{
key: ['Michael'],
value: 6627.0,
name: ['Michael'],
time: 694224000000.0,
},
],
'725846400000000000': [
{
key: ['Christopher'],
value: 0,
name: ['Christopher'],
time: 725846400000.0,
},
{
key: ['Daniel'],
value: 0,
name: ['Daniel'],
time: 725846400000.0,
},
{
key: ['David'],
value: 0,
name: ['David'],
time: 725846400000.0,
},
{
key: ['Jennifer'],
value: 0,
name: ['Jennifer'],
time: 725846400000.0,
},
{
key: ['Michael'],
value: 5839.0,
name: ['Michael'],
time: 725846400000.0,
},
],
};

View File

@@ -1,25 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..",
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
},
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.test.*",
"src/**/*.stories.*"],
"references": [
{ "path": "../../packages/superset-core" },
{ "path": "../../packages/superset-ui-core" },
{ "path": "../../packages/superset-ui-chart-controls" }
]
}

View File

@@ -1,113 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.20.0](https://github.com/apache/superset/compare/v2021.41.0...v0.20.0) (2024-09-09)
### Bug Fixes
- adding missing examples for bubble chart, bullet chart, calendar heatmap chart and country map chart in the gallery ([#22523](https://github.com/apache/superset/issues/22523)) ([839ec7c](https://github.com/apache/superset/commit/839ec7ceacc66c65928fd0ddead2b014db3d5563))
- Adds the Deprecated label to Time-series Percent Change chart ([#30148](https://github.com/apache/superset/issues/30148)) ([5e42d7a](https://github.com/apache/superset/commit/5e42d7aed0d11c7aac91ab19088d2632e49da614))
- **area chart legacy:** tool tip shows actual value rather than y axi… ([#23469](https://github.com/apache/superset/issues/23469)) ([db9ca20](https://github.com/apache/superset/commit/db9ca20737fecda8eee342b34d62d3b700ef3687))
- **Dashboard:** Color inconsistency on refreshes and conflicts ([#27439](https://github.com/apache/superset/issues/27439)) ([313ee59](https://github.com/apache/superset/commit/313ee596f5435894f857d72be7269d5070c8c964))
- **explore:** Fix chart standalone URL for report/thumbnail generation ([#20673](https://github.com/apache/superset/issues/20673)) ([84d4302](https://github.com/apache/superset/commit/84d4302628d18aa19c13cc5322e68abbc690ea4d))
- **explore:** make SORT-Descending visible if Sort-by has value ([#17726](https://github.com/apache/superset/issues/17726)) ([d5768ab](https://github.com/apache/superset/commit/d5768ab649a70fd4f541ad4982498f622160b220))
- **explore:** Prevent shared controls from checking feature flags outside React render ([#21315](https://github.com/apache/superset/issues/21315)) ([2285ebe](https://github.com/apache/superset/commit/2285ebe72ec4edded6d195052740b7f9f13d1f1b))
- **legacy-chart:** corrupted raw chart data ([#24850](https://github.com/apache/superset/issues/24850)) ([1c5971d](https://github.com/apache/superset/commit/1c5971d3afb70a338444c41943ff90c3a9c03ec3))
- Rename legacy line and area charts ([#28113](https://github.com/apache/superset/issues/28113)) ([b4c4ab7](https://github.com/apache/superset/commit/b4c4ab7790cbeb8d65ec7c1084482c21932e755b))
- Reset sorting bar issue in Barchart ([#19371](https://github.com/apache/superset/issues/19371)) ([94e06c2](https://github.com/apache/superset/commit/94e06c2b6a1f782133bb9ef85a1d46ce7eacf9ba))
- **storybook:** fix broken Storybook stories during development ([#29587](https://github.com/apache/superset/issues/29587)) ([462cda4](https://github.com/apache/superset/commit/462cda400baa00b3bcc4a7f8aded362ca55e18a5))
- Tooltip of area chart shows undefined total ([#24916](https://github.com/apache/superset/issues/24916)) ([ec9e9a4](https://github.com/apache/superset/commit/ec9e9a46f2f092ce56d3ed5a8a9a3ea0214db88a))
- warning of nth-child ([#23638](https://github.com/apache/superset/issues/23638)) ([16cc089](https://github.com/apache/superset/commit/16cc089b198dcdebc2422845aa08d18233c6b3a4))
- Zero values on Dual Line axis bounds ([#23649](https://github.com/apache/superset/issues/23649)) ([d66e6e6](https://github.com/apache/superset/commit/d66e6e6d400db0fee35d73cd43e610cd1c491f4b))
### Features
- Adds the ECharts Bubble chart ([#22107](https://github.com/apache/superset/issues/22107)) ([c81c60c](https://github.com/apache/superset/commit/c81c60c91fbcb09dd63c05f050e18ee09ceebfd6))
- apply standardized form data to tier 2 charts ([#20530](https://github.com/apache/superset/issues/20530)) ([de524bc](https://github.com/apache/superset/commit/de524bc59f011fd361dcdb7d35c2cb51f7eba442))
- **chart & legend:** make to enable show legend by default ([#19927](https://github.com/apache/superset/issues/19927)) ([7b3d0f0](https://github.com/apache/superset/commit/7b3d0f040b050905f7d0901d0227f1cd6b761b56))
- **explore:** Apply denormalization to tier 2 charts form data ([#20524](https://github.com/apache/superset/issues/20524)) ([e12ee59](https://github.com/apache/superset/commit/e12ee59b13822241dca8d8015f1222c477edd4f3))
- **explore:** Denormalize form data in echarts, world map and nvd3 bar and line charts ([#20313](https://github.com/apache/superset/issues/20313)) ([354a899](https://github.com/apache/superset/commit/354a89950c4d001da3e107f60788cea873bd6bf6))
- **explore:** improve UI in the control panel ([#19748](https://github.com/apache/superset/issues/19748)) ([e3a54aa](https://github.com/apache/superset/commit/e3a54aa3c15bdd0c970aa73f898288a408205c97))
- **explore:** standardized controls for time pivot chart ([#21321](https://github.com/apache/superset/issues/21321)) ([79525df](https://github.com/apache/superset/commit/79525dfaf29b810af668e3b6c5a56cd866370d92))
- **formatters:** Add custom d3-time-format locale ([#24263](https://github.com/apache/superset/issues/24263)) ([024cfd8](https://github.com/apache/superset/commit/024cfd86e408ec5f7ddf49a9e90908e2fb2e6b70))
- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11))
- **legacy-preset-chart-nvd3:** add richtooltip in nvd3 bar chart ([#17615](https://github.com/apache/superset/issues/17615)) ([72f3215](https://github.com/apache/superset/commit/72f3215ffc74ead33dba57196aeaf4e1db63fd6c))
- Migrates Dual Line Chart to Mixed Chart ([#23910](https://github.com/apache/superset/issues/23910)) ([f5148ef](https://github.com/apache/superset/commit/f5148ef728ce649697c10fb7aa65982d7dd05638))
- Removes the Multiple Line Charts ([#23933](https://github.com/apache/superset/issues/23933)) ([6ce8592](https://github.com/apache/superset/commit/6ce85921fc103ba0e93b437d473003e6f1b4a42b))
- update time comparison choices (again) ([#17968](https://github.com/apache/superset/issues/17968)) ([05d9cde](https://github.com/apache/superset/commit/05d9cde203b99f8c63106446f0be58668cc9f0c9))
- update time comparison choices (again) ([#22458](https://github.com/apache/superset/issues/22458)) ([9e81c3a](https://github.com/apache/superset/commit/9e81c3a1192a18226d505178d16e1e395917a719))
- **viz picker:** Remove some tags, refactor Recommended section ([#27708](https://github.com/apache/superset/issues/27708)) ([c314999](https://github.com/apache/superset/commit/c3149994ac0d4392e0462421b62cd0c034142082))
# [0.19.0](https://github.com/apache/superset/compare/v2021.41.0...v0.19.0) (2024-09-07)
### Bug Fixes
- adding missing examples for bubble chart, bullet chart, calendar heatmap chart and country map chart in the gallery ([#22523](https://github.com/apache/superset/issues/22523)) ([839ec7c](https://github.com/apache/superset/commit/839ec7ceacc66c65928fd0ddead2b014db3d5563))
- Adds the Deprecated label to Time-series Percent Change chart ([#30148](https://github.com/apache/superset/issues/30148)) ([5e42d7a](https://github.com/apache/superset/commit/5e42d7aed0d11c7aac91ab19088d2632e49da614))
- **area chart legacy:** tool tip shows actual value rather than y axi… ([#23469](https://github.com/apache/superset/issues/23469)) ([db9ca20](https://github.com/apache/superset/commit/db9ca20737fecda8eee342b34d62d3b700ef3687))
- **Dashboard:** Color inconsistency on refreshes and conflicts ([#27439](https://github.com/apache/superset/issues/27439)) ([313ee59](https://github.com/apache/superset/commit/313ee596f5435894f857d72be7269d5070c8c964))
- **explore:** Fix chart standalone URL for report/thumbnail generation ([#20673](https://github.com/apache/superset/issues/20673)) ([84d4302](https://github.com/apache/superset/commit/84d4302628d18aa19c13cc5322e68abbc690ea4d))
- **explore:** make SORT-Descending visible if Sort-by has value ([#17726](https://github.com/apache/superset/issues/17726)) ([d5768ab](https://github.com/apache/superset/commit/d5768ab649a70fd4f541ad4982498f622160b220))
- **explore:** Prevent shared controls from checking feature flags outside React render ([#21315](https://github.com/apache/superset/issues/21315)) ([2285ebe](https://github.com/apache/superset/commit/2285ebe72ec4edded6d195052740b7f9f13d1f1b))
- **legacy-chart:** corrupted raw chart data ([#24850](https://github.com/apache/superset/issues/24850)) ([1c5971d](https://github.com/apache/superset/commit/1c5971d3afb70a338444c41943ff90c3a9c03ec3))
- Rename legacy line and area charts ([#28113](https://github.com/apache/superset/issues/28113)) ([b4c4ab7](https://github.com/apache/superset/commit/b4c4ab7790cbeb8d65ec7c1084482c21932e755b))
- Reset sorting bar issue in Barchart ([#19371](https://github.com/apache/superset/issues/19371)) ([94e06c2](https://github.com/apache/superset/commit/94e06c2b6a1f782133bb9ef85a1d46ce7eacf9ba))
- **storybook:** fix broken Storybook stories during development ([#29587](https://github.com/apache/superset/issues/29587)) ([462cda4](https://github.com/apache/superset/commit/462cda400baa00b3bcc4a7f8aded362ca55e18a5))
- Tooltip of area chart shows undefined total ([#24916](https://github.com/apache/superset/issues/24916)) ([ec9e9a4](https://github.com/apache/superset/commit/ec9e9a46f2f092ce56d3ed5a8a9a3ea0214db88a))
- warning of nth-child ([#23638](https://github.com/apache/superset/issues/23638)) ([16cc089](https://github.com/apache/superset/commit/16cc089b198dcdebc2422845aa08d18233c6b3a4))
- Zero values on Dual Line axis bounds ([#23649](https://github.com/apache/superset/issues/23649)) ([d66e6e6](https://github.com/apache/superset/commit/d66e6e6d400db0fee35d73cd43e610cd1c491f4b))
### Features
- Adds the ECharts Bubble chart ([#22107](https://github.com/apache/superset/issues/22107)) ([c81c60c](https://github.com/apache/superset/commit/c81c60c91fbcb09dd63c05f050e18ee09ceebfd6))
- apply standardized form data to tier 2 charts ([#20530](https://github.com/apache/superset/issues/20530)) ([de524bc](https://github.com/apache/superset/commit/de524bc59f011fd361dcdb7d35c2cb51f7eba442))
- **chart & legend:** make to enable show legend by default ([#19927](https://github.com/apache/superset/issues/19927)) ([7b3d0f0](https://github.com/apache/superset/commit/7b3d0f040b050905f7d0901d0227f1cd6b761b56))
- **explore:** Apply denormalization to tier 2 charts form data ([#20524](https://github.com/apache/superset/issues/20524)) ([e12ee59](https://github.com/apache/superset/commit/e12ee59b13822241dca8d8015f1222c477edd4f3))
- **explore:** Denormalize form data in echarts, world map and nvd3 bar and line charts ([#20313](https://github.com/apache/superset/issues/20313)) ([354a899](https://github.com/apache/superset/commit/354a89950c4d001da3e107f60788cea873bd6bf6))
- **explore:** improve UI in the control panel ([#19748](https://github.com/apache/superset/issues/19748)) ([e3a54aa](https://github.com/apache/superset/commit/e3a54aa3c15bdd0c970aa73f898288a408205c97))
- **explore:** standardized controls for time pivot chart ([#21321](https://github.com/apache/superset/issues/21321)) ([79525df](https://github.com/apache/superset/commit/79525dfaf29b810af668e3b6c5a56cd866370d92))
- **formatters:** Add custom d3-time-format locale ([#24263](https://github.com/apache/superset/issues/24263)) ([024cfd8](https://github.com/apache/superset/commit/024cfd86e408ec5f7ddf49a9e90908e2fb2e6b70))
- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11))
- **legacy-preset-chart-nvd3:** add richtooltip in nvd3 bar chart ([#17615](https://github.com/apache/superset/issues/17615)) ([72f3215](https://github.com/apache/superset/commit/72f3215ffc74ead33dba57196aeaf4e1db63fd6c))
- Migrates Dual Line Chart to Mixed Chart ([#23910](https://github.com/apache/superset/issues/23910)) ([f5148ef](https://github.com/apache/superset/commit/f5148ef728ce649697c10fb7aa65982d7dd05638))
- Removes the Multiple Line Charts ([#23933](https://github.com/apache/superset/issues/23933)) ([6ce8592](https://github.com/apache/superset/commit/6ce85921fc103ba0e93b437d473003e6f1b4a42b))
- update time comparison choices (again) ([#17968](https://github.com/apache/superset/issues/17968)) ([05d9cde](https://github.com/apache/superset/commit/05d9cde203b99f8c63106446f0be58668cc9f0c9))
- update time comparison choices (again) ([#22458](https://github.com/apache/superset/issues/22458)) ([9e81c3a](https://github.com/apache/superset/commit/9e81c3a1192a18226d505178d16e1e395917a719))
- **viz picker:** Remove some tags, refactor Recommended section ([#27708](https://github.com/apache/superset/issues/27708)) ([c314999](https://github.com/apache/superset/commit/c3149994ac0d4392e0462421b62cd0c034142082))
# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30)
**Note:** Version bump only for package @superset-ui/legacy-preset-chart-nvd3
## [0.17.63](https://github.com/apache-superset/superset-ui/compare/v0.17.62...v0.17.63) (2021-07-02)
**Note:** Version bump only for package @superset-ui/legacy-preset-chart-nvd3
## [0.17.62](https://github.com/apache-superset/superset-ui/compare/v0.17.61...v0.17.62) (2021-07-02)
**Note:** Version bump only for package @superset-ui/legacy-preset-chart-nvd3
## [0.17.61](https://github.com/apache-superset/superset-ui/compare/v0.17.60...v0.17.61) (2021-07-02)
**Note:** Version bump only for package @superset-ui/legacy-preset-chart-nvd3

View File

@@ -1,64 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
## @superset-ui/legacy-preset-chart-nvd3
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-nvd3.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/legacy-preset-chart-nvd3)
[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Flegacy-preset-chart-nvd3?style=flat)](https://libraries.io/npm/@superset-ui%2Flegacy-preset-chart-nvd3)
This plugin provides Big Number for Superset.
### Usage
Import the preset and register. This will register all the chart plugins under nvd3.
```js
import { NVD3ChartPreset } from '@superset-ui/legacy-preset-chart-nvd3';
new NVD3ChartPreset().register();
```
or register charts one by one. Configure `key`, which can be any `string`, and register the plugin.
This `key` will be used to lookup this chart throughout the app.
```js
import {
AreaChartPlugin,
LineChartPlugin,
} from '@superset-ui/legacy-preset-chart-nvd3';
new AreaChartPlugin().configure({ key: 'area' }).register();
new LineChartPlugin().configure({ key: 'line' }).register();
```
Then use it via `SuperChart`. See
[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-nvd3)
for more details.
```js
<SuperChart
chartType="line"
width={600}
height={600}
formData={...}
queriesData={[{
data: {...},
}]}
/>
```

View File

@@ -1,49 +0,0 @@
{
"name": "@superset-ui/legacy-preset-chart-nvd3",
"version": "0.20.3",
"description": "Superset Legacy Chart - NVD3",
"sideEffects": [
"*.css"
],
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "https://github.com/apache/superset.git",
"directory": "superset-frontend/packages/legacy-preset-chart-nvd3"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache/superset/issues"
},
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-preset-chart-nvd3#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"d3": "^3.5.17",
"d3-tip": "^0.9.1",
"fast-safe-stringify": "^2.1.1",
"lodash": "^4.18.1",
"nvd3-fork": "^2.0.5",
"dompurify": "^3.4.12",
"prop-types": "^15.8.1",
"urijs": "^1.19.11",
"lodash-es": "^4.18.1"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"dayjs": "^1.11.21",
"react": "^18.3.0"
}
}

View File

@@ -1,81 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SuperChart, VizType } from '@superset-ui/core';
import { EchartsBoxPlotChartPlugin } from '@superset-ui/plugin-chart-echarts';
import { dummyDatasource, withResizableChartDemo } from '@storybook-shared';
import data from './data';
new EchartsBoxPlotChartPlugin().configure({ key: 'box-plot' }).register();
export default {
title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/BoxPlot',
decorators: [withResizableChartDemo],
args: {
colorScheme: 'd3Category10',
whiskerOptions: 'Min/max (no outliers)',
},
argTypes: {
colorScheme: {
control: 'select',
options: [
'supersetColors',
'd3Category10',
'bnbColors',
'googleCategory20c',
],
},
whiskerOptions: {
control: 'select',
options: [
'Tukey',
'Min/max (no outliers)',
'2/98 percentiles',
'9/91 percentiles',
],
},
},
};
export const Basic = ({
colorScheme,
whiskerOptions,
width,
height,
}: {
colorScheme: string;
whiskerOptions: string;
width: number;
height: number;
}) => (
<SuperChart
chartType="box-plot"
width={width}
height={height}
datasource={dummyDatasource}
queriesData={[{ data }]}
formData={{
color_scheme: colorScheme,
viz_type: VizType.BoxPlot,
whisker_options: whiskerOptions,
groupby: ['region'],
metrics: ['sum__SP_POP_TOTL'],
}}
/>
);

View File

@@ -1,78 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable sort-keys, no-magic-numbers */
// Data format for ECharts BoxPlot - requires metric__min, metric__q1, metric__median, etc.
export default [
{
region: 'East Asia & Pacific',
sum__SP_POP_TOTL__min: 1031863394.0,
sum__SP_POP_TOTL__q1: 1384725172.5,
sum__SP_POP_TOTL__median: 1717904169.0,
sum__SP_POP_TOTL__q3: 2032724922.5,
sum__SP_POP_TOTL__max: 2240687901.0,
sum__SP_POP_TOTL__mean: 1681000000.0,
sum__SP_POP_TOTL__count: 50,
sum__SP_POP_TOTL__outliers: [],
},
{
region: 'Europe & Central Asia',
sum__SP_POP_TOTL__min: 660881033.0,
sum__SP_POP_TOTL__q1: 751386460.5,
sum__SP_POP_TOTL__median: 820716895.0,
sum__SP_POP_TOTL__q3: 862814192.5,
sum__SP_POP_TOTL__max: 903095786.0,
sum__SP_POP_TOTL__mean: 799778873.0,
sum__SP_POP_TOTL__count: 50,
sum__SP_POP_TOTL__outliers: [],
},
{
region: 'Latin America & Caribbean',
sum__SP_POP_TOTL__min: 220564224.0,
sum__SP_POP_TOTL__q1: 313690832.5,
sum__SP_POP_TOTL__median: 421490233.0,
sum__SP_POP_TOTL__q3: 529668114.5,
sum__SP_POP_TOTL__max: 626270167.0,
sum__SP_POP_TOTL__mean: 422336714.0,
sum__SP_POP_TOTL__count: 50,
sum__SP_POP_TOTL__outliers: [],
},
{
region: 'South Asia',
sum__SP_POP_TOTL__min: 572036107.0,
sum__SP_POP_TOTL__q1: 772373036.5,
sum__SP_POP_TOTL__median: 1059570231.0,
sum__SP_POP_TOTL__q3: 1398841234.0,
sum__SP_POP_TOTL__max: 1720976995.0,
sum__SP_POP_TOTL__mean: 1104759521.0,
sum__SP_POP_TOTL__count: 50,
sum__SP_POP_TOTL__outliers: [],
},
{
region: 'Sub-Saharan Africa',
sum__SP_POP_TOTL__min: 228268752.0,
sum__SP_POP_TOTL__q1: 320037758.0,
sum__SP_POP_TOTL__median: 467337821.0,
sum__SP_POP_TOTL__q3: 676768689.0,
sum__SP_POP_TOTL__max: 974315323.0,
sum__SP_POP_TOTL__mean: 533345669.0,
sum__SP_POP_TOTL__count: 50,
sum__SP_POP_TOTL__outliers: [1100000000, 1200000000],
},
];

View File

@@ -1,140 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@apache-superset/core/translation';
import {
ControlPanelConfig,
formatSelectOptions,
D3_FORMAT_OPTIONS,
getStandardizedControls,
} from '@superset-ui/chart-controls';
import {
showLegend,
xAxisLabel,
yAxisLabel,
bottomMargin,
xTicksLayout,
xAxisFormat,
yLogScale,
xAxisShowMinmax,
yAxisShowMinmax,
leftMargin,
yAxisBounds,
} from '../NVD3Controls';
const config: ControlPanelConfig = {
controlPanelSections: [
{
label: t('Query'),
expanded: true,
controlSetRows: [
['series'],
['entity'],
['x'],
['y'],
['adhoc_filters'],
['size'],
[
{
name: 'max_bubble_size',
config: {
type: 'SelectControl',
freeForm: true,
label: t('Max Bubble Size'),
default: '25',
choices: formatSelectOptions([
'5',
'10',
'15',
'25',
'50',
'75',
'100',
]),
},
},
],
['limit', null],
],
},
{
label: t('Chart Options'),
expanded: true,
tabOverride: 'customize',
controlSetRows: [['color_scheme'], [showLegend, null]],
},
{
label: t('X Axis'),
expanded: true,
tabOverride: 'customize',
controlSetRows: [
[xAxisLabel, leftMargin],
[
{
name: xAxisFormat.name,
config: {
...xAxisFormat.config,
default: 'SMART_NUMBER',
choices: D3_FORMAT_OPTIONS,
},
},
xTicksLayout,
],
[
{
name: 'x_log_scale',
config: {
type: 'CheckboxControl',
label: t('X Log Scale'),
default: false,
renderTrigger: true,
description: t('Use a log scale for the X-axis'),
},
},
xAxisShowMinmax,
],
],
},
{
label: t('Y Axis'),
expanded: true,
tabOverride: 'customize',
controlSetRows: [
[yAxisLabel, bottomMargin],
['y_axis_format', null],
[yLogScale, yAxisShowMinmax],
[yAxisBounds],
],
},
],
controlOverrides: {
color_scheme: {
renderTrigger: false,
},
},
formDataOverrides: formData => ({
...formData,
series: getStandardizedControls().shiftColumn(),
entity: getStandardizedControls().shiftColumn(),
x: getStandardizedControls().shiftMetric(),
y: getStandardizedControls().shiftMetric(),
size: getStandardizedControls().shiftMetric(),
}),
};
export default config;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -1,103 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SuperChart, VizType } from '@superset-ui/core';
import { BubbleChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3';
import { dummyDatasource, withResizableChartDemo } from '@storybook-shared';
import data from './data';
new BubbleChartPlugin().configure({ key: VizType.LegacyBubble }).register();
export default {
title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/Bubble',
decorators: [withResizableChartDemo],
args: {
colorScheme: 'd3Category10',
maxBubbleSize: 50,
showLegend: true,
xLogScale: false,
yLogScale: false,
},
argTypes: {
colorScheme: {
control: 'select',
options: [
'supersetColors',
'd3Category10',
'bnbColors',
'googleCategory20c',
],
},
maxBubbleSize: {
control: { type: 'range', min: 10, max: 100, step: 5 },
},
showLegend: { control: 'boolean' },
xLogScale: { control: 'boolean' },
yLogScale: { control: 'boolean' },
},
};
export const Basic = ({
colorScheme,
maxBubbleSize,
showLegend,
xLogScale,
yLogScale,
width,
height,
}: {
colorScheme: string;
maxBubbleSize: number;
showLegend: boolean;
xLogScale: boolean;
yLogScale: boolean;
width: number;
height: number;
}) => (
<SuperChart
chartType={VizType.LegacyBubble}
width={width}
height={height}
datasource={dummyDatasource}
queriesData={[{ data }]}
formData={{
annotation_data: {},
bottom_margin: 'auto',
color_scheme: colorScheme,
entity: 'country_name',
left_margin: 'auto',
max_bubble_size: String(maxBubbleSize),
series: 'region',
show_legend: showLegend,
size: 'sum__SP_POP_TOTL',
viz_type: VizType.LegacyBubble,
x: 'sum__SP_RUR_TOTL_ZS',
x_axis_format: '.3s',
x_axis_label: 'Rural Population %',
x_axis_showminmax: false,
x_log_scale: xLogScale,
x_ticks_layout: 'auto',
y: 'sum__SP_DYN_LE00_IN',
y_axis_format: '.3s',
y_axis_label: 'Life Expectancy',
y_axis_showminmax: false,
y_log_scale: yLogScale,
}}
/>
);

View File

@@ -1,357 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable sort-keys, no-magic-numbers */
export default [
{
key: 'East Asia & Pacific',
values: [
{
country_name: 'China',
region: 'East Asia & Pacific',
sum__SP_POP_TOTL: 1344130000.0,
sum__SP_RUR_TOTL_ZS: 49.427,
sum__SP_DYN_LE00_IN: 75.042,
x: 49.427,
y: 75.042,
size: 1344130000.0,
shape: 'circle',
group: 'East Asia & Pacific',
},
{
country_name: 'Indonesia',
region: 'East Asia & Pacific',
sum__SP_POP_TOTL: 244808254.0,
sum__SP_RUR_TOTL_ZS: 49.288,
sum__SP_DYN_LE00_IN: 70.3915609756,
x: 49.288,
y: 70.3915609756,
size: 244808254.0,
shape: 'circle',
group: 'East Asia & Pacific',
},
{
country_name: 'Japan',
region: 'East Asia & Pacific',
sum__SP_POP_TOTL: 127817277.0,
sum__SP_RUR_TOTL_ZS: 8.752,
sum__SP_DYN_LE00_IN: 82.5912195122,
x: 8.752,
y: 82.5912195122,
size: 127817277.0,
shape: 'circle',
group: 'East Asia & Pacific',
},
{
country_name: 'Philippines',
region: 'East Asia & Pacific',
sum__SP_POP_TOTL: 94501233.0,
sum__SP_RUR_TOTL_ZS: 54.983,
sum__SP_DYN_LE00_IN: 68.3914878049,
x: 54.983,
y: 68.3914878049,
size: 94501233.0,
shape: 'circle',
group: 'East Asia & Pacific',
},
{
country_name: 'Vietnam',
region: 'East Asia & Pacific',
sum__SP_POP_TOTL: 87840000.0,
sum__SP_RUR_TOTL_ZS: 68.971,
sum__SP_DYN_LE00_IN: 75.457902439,
x: 68.971,
y: 75.457902439,
size: 87840000.0,
shape: 'circle',
group: 'East Asia & Pacific',
},
{
country_name: 'Thailand',
region: 'East Asia & Pacific',
sum__SP_POP_TOTL: 66902958.0,
sum__SP_RUR_TOTL_ZS: 54.606,
sum__SP_DYN_LE00_IN: 74.008902439,
x: 54.606,
y: 74.008902439,
size: 66902958.0,
shape: 'circle',
group: 'East Asia & Pacific',
},
{
country_name: 'Myanmar',
region: 'East Asia & Pacific',
sum__SP_POP_TOTL: 52125411.0,
sum__SP_RUR_TOTL_ZS: 68.065,
sum__SP_DYN_LE00_IN: 64.7612439024,
x: 68.065,
y: 64.7612439024,
size: 52125411.0,
shape: 'circle',
group: 'East Asia & Pacific',
},
],
},
{
key: 'South Asia',
values: [
{
country_name: 'India',
region: 'South Asia',
sum__SP_POP_TOTL: 1247446011.0,
sum__SP_RUR_TOTL_ZS: 68.724,
sum__SP_DYN_LE00_IN: 65.9584878049,
x: 68.724,
y: 65.9584878049,
size: 1247446011.0,
shape: 'circle',
group: 'South Asia',
},
{
country_name: 'Pakistan',
region: 'South Asia',
sum__SP_POP_TOTL: 173669648.0,
sum__SP_RUR_TOTL_ZS: 62.993,
sum__SP_DYN_LE00_IN: 66.2838780488,
x: 62.993,
y: 66.2838780488,
size: 173669648.0,
shape: 'circle',
group: 'South Asia',
},
{
country_name: 'Bangladesh',
region: 'South Asia',
sum__SP_POP_TOTL: 153405612.0,
sum__SP_RUR_TOTL_ZS: 68.775,
sum__SP_DYN_LE00_IN: 69.891804878,
x: 68.775,
y: 69.891804878,
size: 153405612.0,
shape: 'circle',
group: 'South Asia',
},
],
},
{
key: 'North America',
values: [
{
country_name: 'United States',
region: 'North America',
sum__SP_POP_TOTL: 311721632.0,
sum__SP_RUR_TOTL_ZS: 19.06,
sum__SP_DYN_LE00_IN: 78.6414634146,
x: 19.06,
y: 78.6414634146,
size: 311721632.0,
shape: 'circle',
group: 'North America',
},
],
},
{
key: 'Latin America & Caribbean',
values: [
{
country_name: 'Brazil',
region: 'Latin America & Caribbean',
sum__SP_POP_TOTL: 200517584.0,
sum__SP_RUR_TOTL_ZS: 15.377,
sum__SP_DYN_LE00_IN: 73.3473658537,
x: 15.377,
y: 73.3473658537,
size: 200517584.0,
shape: 'circle',
group: 'Latin America & Caribbean',
},
{
country_name: 'Mexico',
region: 'Latin America & Caribbean',
sum__SP_POP_TOTL: 120365271.0,
sum__SP_RUR_TOTL_ZS: 21.882,
sum__SP_DYN_LE00_IN: 76.9141707317,
x: 21.882,
y: 76.9141707317,
size: 120365271.0,
shape: 'circle',
group: 'Latin America & Caribbean',
},
],
},
{
key: 'Sub-Saharan Africa',
values: [
{
country_name: 'Nigeria',
region: 'Sub-Saharan Africa',
sum__SP_POP_TOTL: 163770669.0,
sum__SP_RUR_TOTL_ZS: 55.638,
sum__SP_DYN_LE00_IN: 51.7102439024,
x: 55.638,
y: 51.7102439024,
size: 163770669.0,
shape: 'circle',
group: 'Sub-Saharan Africa',
},
{
country_name: 'Ethiopia',
region: 'Sub-Saharan Africa',
sum__SP_POP_TOTL: 89858696.0,
sum__SP_RUR_TOTL_ZS: 82.265,
sum__SP_DYN_LE00_IN: 62.2528536585,
x: 82.265,
y: 62.2528536585,
size: 89858696.0,
shape: 'circle',
group: 'Sub-Saharan Africa',
},
{
country_name: 'Congo, Dem. Rep.',
region: 'Sub-Saharan Africa',
sum__SP_POP_TOTL: 68087376.0,
sum__SP_RUR_TOTL_ZS: 59.558,
sum__SP_DYN_LE00_IN: 49.3007073171,
x: 59.558,
y: 49.3007073171,
size: 68087376.0,
shape: 'circle',
group: 'Sub-Saharan Africa',
},
{
country_name: 'South Africa',
region: 'Sub-Saharan Africa',
sum__SP_POP_TOTL: 51553479.0,
sum__SP_RUR_TOTL_ZS: 37.254,
sum__SP_DYN_LE00_IN: 55.2956585366,
x: 37.254,
y: 55.2956585366,
size: 51553479.0,
shape: 'circle',
group: 'Sub-Saharan Africa',
},
],
},
{
key: 'Europe & Central Asia',
values: [
{
country_name: 'Russian Federation',
region: 'Europe & Central Asia',
sum__SP_POP_TOTL: 142960868.0,
sum__SP_RUR_TOTL_ZS: 26.268,
sum__SP_DYN_LE00_IN: 69.6585365854,
x: 26.268,
y: 69.6585365854,
size: 142960868.0,
shape: 'circle',
group: 'Europe & Central Asia',
},
{
country_name: 'Germany',
region: 'Europe & Central Asia',
sum__SP_POP_TOTL: 81797673.0,
sum__SP_RUR_TOTL_ZS: 25.512,
sum__SP_DYN_LE00_IN: 80.7414634146,
x: 25.512,
y: 80.7414634146,
size: 81797673.0,
shape: 'circle',
group: 'Europe & Central Asia',
},
{
country_name: 'Turkey',
region: 'Europe & Central Asia',
sum__SP_POP_TOTL: 73199372.0,
sum__SP_RUR_TOTL_ZS: 28.718,
sum__SP_DYN_LE00_IN: 74.5404878049,
x: 28.718,
y: 74.5404878049,
size: 73199372.0,
shape: 'circle',
group: 'Europe & Central Asia',
},
{
country_name: 'France',
region: 'Europe & Central Asia',
sum__SP_POP_TOTL: 65342776.0,
sum__SP_RUR_TOTL_ZS: 21.416,
sum__SP_DYN_LE00_IN: 82.1146341463,
x: 21.416,
y: 82.1146341463,
size: 65342776.0,
shape: 'circle',
group: 'Europe & Central Asia',
},
{
country_name: 'United Kingdom',
region: 'Europe & Central Asia',
sum__SP_POP_TOTL: 63258918.0,
sum__SP_RUR_TOTL_ZS: 18.43,
sum__SP_DYN_LE00_IN: 80.9512195122,
x: 18.43,
y: 80.9512195122,
size: 63258918.0,
shape: 'circle',
group: 'Europe & Central Asia',
},
{
country_name: 'Italy',
region: 'Europe & Central Asia',
sum__SP_POP_TOTL: 59379449.0,
sum__SP_RUR_TOTL_ZS: 31.556,
sum__SP_DYN_LE00_IN: 82.187804878,
x: 31.556,
y: 82.187804878,
size: 59379449.0,
shape: 'circle',
group: 'Europe & Central Asia',
},
],
},
{
key: 'Middle East & North Africa',
values: [
{
country_name: 'Egypt, Arab Rep.',
region: 'Middle East & North Africa',
sum__SP_POP_TOTL: 83787634.0,
sum__SP_RUR_TOTL_ZS: 57.0,
sum__SP_DYN_LE00_IN: 70.6785609756,
x: 57.0,
y: 70.6785609756,
size: 83787634.0,
shape: 'circle',
group: 'Middle East & North Africa',
},
{
country_name: 'Iran, Islamic Rep.',
region: 'Middle East & North Africa',
sum__SP_POP_TOTL: 75184322.0,
sum__SP_RUR_TOTL_ZS: 28.8,
sum__SP_DYN_LE00_IN: 73.4493170732,
x: 28.8,
y: 73.4493170732,
size: 75184322.0,
shape: 'circle',
group: 'Middle East & North Africa',
},
],
},
];

View File

@@ -1,51 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@apache-superset/core/translation';
import { ChartMetadata, ChartPlugin } from '@superset-ui/core';
import transformProps from '../transformProps';
import example from './images/example.jpg';
import exampleDark from './images/example-dark.jpg';
import thumbnail from './images/thumbnail.png';
import thumbnailDark from './images/thumbnail-dark.png';
import controlPanel from './controlPanel';
const metadata = new ChartMetadata({
category: t('KPI'),
credits: ['http://nvd3.org'],
description: t(
'Showcases the progress of a single metric against a given target. The higher the fill, the closer the metric is to the target.',
),
exampleGallery: [{ url: example, urlDark: exampleDark }],
name: t('Bullet Chart'),
tags: [t('Business'), t('Legacy'), t('Report'), t('nvd3')],
thumbnail,
thumbnailDark,
useLegacyApi: true,
});
export default class BulletChartPlugin extends ChartPlugin {
constructor() {
super({
loadChart: () => import('../ReactNVD3'),
metadata,
transformProps,
controlPanel,
});
}
}

View File

@@ -1,87 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SuperChart, VizType } from '@superset-ui/core';
import { BulletChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3';
import { dummyDatasource, withResizableChartDemo } from '@storybook-shared';
import data from './data';
new BulletChartPlugin().configure({ key: VizType.Bullet }).register();
export default {
title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/Bullet',
decorators: [withResizableChartDemo],
args: {
ranges: '0, 50, 75, 100',
rangeLabels: 'Low, Medium, High',
markers: '65',
markerLabels: 'Target',
},
argTypes: {
ranges: {
control: 'text',
description: 'Comma-separated range values',
},
rangeLabels: {
control: 'text',
description: 'Comma-separated range labels',
},
markers: {
control: 'text',
description: 'Comma-separated marker values',
},
markerLabels: {
control: 'text',
description: 'Comma-separated marker labels',
},
},
};
export const Basic = ({
ranges,
rangeLabels,
markers,
markerLabels,
width,
height,
}: {
ranges: string;
rangeLabels: string;
markers: string;
markerLabels: string;
width: number;
height: number;
}) => (
<SuperChart
chartType={VizType.Bullet}
width={width}
height={height}
datasource={dummyDatasource}
queriesData={[{ data }]}
formData={{
marker_labels: markerLabels,
marker_line_labels: '',
marker_lines: '',
markers,
range_labels: rangeLabels,
ranges,
viz_type: VizType.Bullet,
}}
/>
);

View File

@@ -1,76 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@apache-superset/core/translation';
import {
ControlPanelConfig,
getStandardizedControls,
sections,
} from '@superset-ui/chart-controls';
import {
xAxisLabel,
yAxisLabel,
bottomMargin,
xTicksLayout,
xAxisFormat,
yLogScale,
yAxisBounds,
xAxisShowMinmax,
yAxisShowMinmax,
leftMargin,
timeSeriesSection,
} from '../NVD3Controls';
const config: ControlPanelConfig = {
controlPanelSections: [
sections.legacyTimeseriesTime,
timeSeriesSection[0],
{
label: t('Chart Options'),
expanded: true,
controlSetRows: [['color_scheme']],
},
{
label: t('X Axis'),
expanded: true,
controlSetRows: [
[xAxisLabel, bottomMargin],
[xTicksLayout, xAxisFormat],
[xAxisShowMinmax, null],
],
},
{
label: t('Y Axis'),
expanded: true,
controlSetRows: [
[yAxisLabel, leftMargin],
[yAxisShowMinmax, yLogScale],
['y_axis_format', yAxisBounds],
],
},
timeSeriesSection[1],
sections.annotations,
],
formDataOverrides: formData => ({
...formData,
groupby: getStandardizedControls().popAllColumns(),
metrics: getStandardizedControls().popAllMetrics(),
}),
};
export default config;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -1,62 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@apache-superset/core/translation';
import { ChartMetadata, ChartPlugin, ChartLabel } from '@superset-ui/core';
import transformProps from '../transformProps';
import thumbnail from './images/thumbnail.png';
import thumbnailDark from './images/thumbnail-dark.png';
import example from './images/example.jpg';
import exampleDark from './images/example-dark.jpg';
import controlPanel from './controlPanel';
const metadata = new ChartMetadata({
category: t('Evolution'),
credits: ['http://nvd3.org'],
description: t(
'Visualizes many different time-series objects in a single chart. This chart is being deprecated and we recommend using the Time-series Chart instead.',
),
exampleGallery: [{ url: example, urlDark: exampleDark }],
label: ChartLabel.Deprecated,
name: t('Time-series Percent Change'),
tags: [
t('Legacy'),
t('Time'),
t('nvd3'),
t('Advanced-Analytics'),
t('Comparison'),
t('Line'),
t('Percentages'),
t('Predictive'),
t('Trend'),
],
thumbnail,
thumbnailDark,
useLegacyApi: true,
});
export default class CompareChartPlugin extends ChartPlugin {
constructor() {
super({
loadChart: () => import('../ReactNVD3'),
metadata,
transformProps,
controlPanel,
});
}
}

View File

@@ -1,208 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SuperChart, VizType } from '@superset-ui/core';
import { CompareChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3';
import { dummyDatasource, withResizableChartDemo } from '@storybook-shared';
import data from './data';
new CompareChartPlugin().configure({ key: VizType.Compare }).register();
export default {
title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/Compare',
decorators: [withResizableChartDemo],
args: {
colorScheme: 'd3Category10',
contribution: false,
yLogScale: false,
},
argTypes: {
colorScheme: {
control: 'select',
options: [
'supersetColors',
'd3Category10',
'bnbColors',
'googleCategory20c',
],
},
contribution: {
control: 'boolean',
description: 'Compute contribution to total',
},
yLogScale: { control: 'boolean' },
},
};
export const Basic = ({
colorScheme,
contribution,
yLogScale,
width,
height,
}: {
colorScheme: string;
contribution: boolean;
yLogScale: boolean;
width: number;
height: number;
}) => (
<SuperChart
chartType="compare"
width={width}
height={height}
datasource={dummyDatasource}
queriesData={[{ data }]}
formData={{
bottom_margin: 'auto',
color_scheme: colorScheme,
contribution,
left_margin: 'auto',
viz_type: VizType.Compare,
x_axis_format: 'smart_date',
x_axis_label: '',
x_axis_showminmax: false,
x_ticks_layout: 'auto',
y_axis_bounds: [null, null],
y_axis_format: '.3s',
y_axis_label: '',
y_axis_showminmax: false,
y_log_scale: yLogScale,
}}
/>
);
/* eslint-disable no-magic-numbers */
const timeFormatData = [
{
key: ['Africa and Middle East'],
values: [
{ x: 1606348800000, y: 3985 },
{ x: 1606435200000, y: 5882 },
{ x: 1606521600000, y: 7983 },
{ x: 1606608000000, y: 16462 },
{ x: 1606694400000, y: 5542 },
{ x: 1606780800000, y: 2825 },
],
},
{
key: ['Asia'],
values: [
{ x: 1606348800000, y: 34837 },
{ x: 1606435200000, y: 40718 },
{ x: 1606521600000, y: 58507 },
{ x: 1606608000000, y: 110120 },
{ x: 1606694400000, y: 43443 },
{ x: 1606780800000, y: 33538 },
],
},
{
key: ['Australia'],
values: [
{ x: 1606348800000, y: 12975 },
{ x: 1606435200000, y: 18471 },
{ x: 1606521600000, y: 17880 },
{ x: 1606608000000, y: 52204 },
{ x: 1606694400000, y: 26690 },
{ x: 1606780800000, y: 16423 },
],
},
{
key: ['Europe'],
values: [
{ x: 1606348800000, y: 127029 },
{ x: 1606435200000, y: 177637 },
{ x: 1606521600000, y: 172653 },
{ x: 1606608000000, y: 203654 },
{ x: 1606694400000, y: 79200 },
{ x: 1606780800000, y: 45238 },
],
},
{
key: ['LatAm'],
values: [
{ x: 1606348800000, y: 22513 },
{ x: 1606435200000, y: 24594 },
{ x: 1606521600000, y: 32578 },
{ x: 1606608000000, y: 34733 },
{ x: 1606694400000, y: 71696 },
{ x: 1606780800000, y: 166611 },
],
},
{
key: ['North America'],
values: [
{ x: 1606348800000, y: 104596 },
{ x: 1606435200000, y: 109850 },
{ x: 1606521600000, y: 136873 },
{ x: 1606608000000, y: 133243 },
{ x: 1606694400000, y: 327739 },
{ x: 1606780800000, y: 162711 },
],
},
];
export const timeFormat = () => (
<SuperChart
chartType="compare"
width={400}
height={400}
datasource={dummyDatasource}
queriesData={[{ data: timeFormatData }]}
formData={{
datasource: '24771__table',
vizType: VizType.Compare,
urlParams: {},
timeRangeEndpoints: ['inclusive', 'exclusive'],
granularitySqla: '__time',
timeGrainSqla: 'P1D',
timeRange: 'Last week',
metrics: ['random_metric'],
adhocFilters: [],
groupby: ['dim_origin_region'],
timeseriesLimitMetric: null,
orderDesc: true,
contribution: false,
rowLimit: 10000,
colorScheme: 'd3Category10',
labelColors: {},
xAxisLabel: '',
bottomMargin: 'auto',
xTicksLayout: 'auto',
xAxisFormat: 'smart_date',
xAxisShowminmax: false,
yAxisLabel: '',
leftMargin: 'auto',
yAxisShowminmax: false,
yLogScale: false,
yAxisFormat: 'SMART_NUMBER',
yAxisBounds: [null, null],
rollingType: 'None',
comparisonType: 'values',
resampleRule: null,
resampleMethod: null,
annotationLayers: [],
appliedTimeExtras: {},
where: '',
having: '',
havingFilters: [],
filters: [],
}}
/>
);

View File

@@ -1,927 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable sort-keys, no-magic-numbers */
export default [
{
key: ['Christopher'],
values: [
{
x: -157766400000.0,
y: 24703,
},
{
x: -126230400000.0,
y: 27861,
},
{
x: -94694400000.0,
y: 29436,
},
{
x: -63158400000.0,
y: 31463,
},
{
x: -31536000000.0,
y: 35718,
},
{
x: 0.0,
y: 41758,
},
{
x: 31536000000.0,
y: 48172,
},
{
x: 63072000000.0,
y: 52092,
},
{
x: 94694400000.0,
y: 48217,
},
{
x: 126230400000.0,
y: 48476,
},
{
x: 157766400000.0,
y: 46438,
},
{
x: 189302400000.0,
y: 45086,
},
{
x: 220924800000.0,
y: 46610,
},
{
x: 252460800000.0,
y: 47107,
},
{
x: 283996800000.0,
y: 50514,
},
{
x: 315532800000.0,
y: 48969,
},
{
x: 347155200000.0,
y: 50108,
},
{
x: 378691200000.0,
y: 59055,
},
{
x: 410227200000.0,
y: 59188,
},
{
x: 441763200000.0,
y: 59859,
},
{
x: 473385600000.0,
y: 59516,
},
{
x: 504921600000.0,
y: 56633,
},
{
x: 536457600000.0,
y: 54466,
},
{
x: 567993600000.0,
y: 52996,
},
{
x: 599616000000.0,
y: 53205,
},
{
x: 631152000000.0,
y: 52322,
},
{
x: 662688000000.0,
y: 47109,
},
{
x: 694224000000.0,
y: 42470,
},
{
x: 725846400000.0,
y: 38257,
},
{
x: 757382400000.0,
y: 34823,
},
{
x: 788918400000.0,
y: 32728,
},
{
x: 820454400000.0,
y: 30988,
},
{
x: 852076800000.0,
y: 29179,
},
{
x: 883612800000.0,
y: 27083,
},
{
x: 915148800000.0,
y: 25700,
},
{
x: 946684800000.0,
y: 24959,
},
{
x: 978307200000.0,
y: 23180,
},
{
x: 1009843200000.0,
y: 21731,
},
{
x: 1041379200000.0,
y: 20793,
},
{
x: 1072915200000.0,
y: 19739,
},
{
x: 1104537600000.0,
y: 19190,
},
{
x: 1136073600000.0,
y: 19674,
},
{
x: 1167609600000.0,
y: 19986,
},
{
x: 1199145600000.0,
y: 17771,
},
],
},
{
key: ['David'],
values: [
{
x: -157766400000.0,
y: 67646,
},
{
x: -126230400000.0,
y: 66207,
},
{
x: -94694400000.0,
y: 66581,
},
{
x: -63158400000.0,
y: 63531,
},
{
x: -31536000000.0,
y: 63502,
},
{
x: 0.0,
y: 61570,
},
{
x: 31536000000.0,
y: 52948,
},
{
x: 63072000000.0,
y: 46218,
},
{
x: 94694400000.0,
y: 40968,
},
{
x: 126230400000.0,
y: 41654,
},
{
x: 157766400000.0,
y: 39019,
},
{
x: 189302400000.0,
y: 39165,
},
{
x: 220924800000.0,
y: 40407,
},
{
x: 252460800000.0,
y: 40533,
},
{
x: 283996800000.0,
y: 41898,
},
{
x: 315532800000.0,
y: 41743,
},
{
x: 347155200000.0,
y: 40486,
},
{
x: 378691200000.0,
y: 40283,
},
{
x: 410227200000.0,
y: 39048,
},
{
x: 441763200000.0,
y: 38346,
},
{
x: 473385600000.0,
y: 38395,
},
{
x: 504921600000.0,
y: 37021,
},
{
x: 536457600000.0,
y: 36672,
},
{
x: 567993600000.0,
y: 35214,
},
{
x: 599616000000.0,
y: 35139,
},
{
x: 631152000000.0,
y: 33661,
},
{
x: 662688000000.0,
y: 30347,
},
{
x: 694224000000.0,
y: 28344,
},
{
x: 725846400000.0,
y: 26947,
},
{
x: 757382400000.0,
y: 24784,
},
{
x: 788918400000.0,
y: 22967,
},
{
x: 820454400000.0,
y: 22941,
},
{
x: 852076800000.0,
y: 21824,
},
{
x: 883612800000.0,
y: 20816,
},
{
x: 915148800000.0,
y: 20267,
},
{
x: 946684800000.0,
y: 19695,
},
{
x: 978307200000.0,
y: 19281,
},
{
x: 1009843200000.0,
y: 18600,
},
{
x: 1041379200000.0,
y: 18557,
},
{
x: 1072915200000.0,
y: 18315,
},
{
x: 1104537600000.0,
y: 18017,
},
{
x: 1136073600000.0,
y: 17510,
},
{
x: 1167609600000.0,
y: 17400,
},
{
x: 1199145600000.0,
y: 16049,
},
],
},
{
key: ['James'],
values: [
{
x: -157766400000.0,
y: 67506,
},
{
x: -126230400000.0,
y: 65036,
},
{
x: -94694400000.0,
y: 61554,
},
{
x: -63158400000.0,
y: 60584,
},
{
x: -31536000000.0,
y: 59824,
},
{
x: 0.0,
y: 61597,
},
{
x: 31536000000.0,
y: 54463,
},
{
x: 63072000000.0,
y: 46960,
},
{
x: 94694400000.0,
y: 42782,
},
{
x: 126230400000.0,
y: 41258,
},
{
x: 157766400000.0,
y: 39471,
},
{
x: 189302400000.0,
y: 38203,
},
{
x: 220924800000.0,
y: 39916,
},
{
x: 252460800000.0,
y: 39783,
},
{
x: 283996800000.0,
y: 39237,
},
{
x: 315532800000.0,
y: 39185,
},
{
x: 347155200000.0,
y: 38176,
},
{
x: 378691200000.0,
y: 38750,
},
{
x: 410227200000.0,
y: 36228,
},
{
x: 441763200000.0,
y: 35728,
},
{
x: 473385600000.0,
y: 35750,
},
{
x: 504921600000.0,
y: 33955,
},
{
x: 536457600000.0,
y: 32552,
},
{
x: 567993600000.0,
y: 32418,
},
{
x: 599616000000.0,
y: 32658,
},
{
x: 631152000000.0,
y: 32288,
},
{
x: 662688000000.0,
y: 30460,
},
{
x: 694224000000.0,
y: 28450,
},
{
x: 725846400000.0,
y: 26193,
},
{
x: 757382400000.0,
y: 24706,
},
{
x: 788918400000.0,
y: 22691,
},
{
x: 820454400000.0,
y: 21122,
},
{
x: 852076800000.0,
y: 20368,
},
{
x: 883612800000.0,
y: 19651,
},
{
x: 915148800000.0,
y: 18508,
},
{
x: 946684800000.0,
y: 17939,
},
{
x: 978307200000.0,
y: 17023,
},
{
x: 1009843200000.0,
y: 16905,
},
{
x: 1041379200000.0,
y: 16832,
},
{
x: 1072915200000.0,
y: 16459,
},
{
x: 1104537600000.0,
y: 16046,
},
{
x: 1136073600000.0,
y: 16139,
},
{
x: 1167609600000.0,
y: 15821,
},
{
x: 1199145600000.0,
y: 14920,
},
],
},
{
key: ['John'],
values: [
{
x: -157766400000.0,
y: 71390,
},
{
x: -126230400000.0,
y: 64858,
},
{
x: -94694400000.0,
y: 61480,
},
{
x: -63158400000.0,
y: 60754,
},
{
x: -31536000000.0,
y: 58644,
},
{
x: 0.0,
y: 58348,
},
{
x: 31536000000.0,
y: 51382,
},
{
x: 63072000000.0,
y: 43028,
},
{
x: 94694400000.0,
y: 39061,
},
{
x: 126230400000.0,
y: 37553,
},
{
x: 157766400000.0,
y: 34970,
},
{
x: 189302400000.0,
y: 33876,
},
{
x: 220924800000.0,
y: 34103,
},
{
x: 252460800000.0,
y: 33895,
},
{
x: 283996800000.0,
y: 35305,
},
{
x: 315532800000.0,
y: 35131,
},
{
x: 347155200000.0,
y: 34761,
},
{
x: 378691200000.0,
y: 34560,
},
{
x: 410227200000.0,
y: 33047,
},
{
x: 441763200000.0,
y: 32484,
},
{
x: 473385600000.0,
y: 31397,
},
{
x: 504921600000.0,
y: 30103,
},
{
x: 536457600000.0,
y: 29462,
},
{
x: 567993600000.0,
y: 29301,
},
{
x: 599616000000.0,
y: 29751,
},
{
x: 631152000000.0,
y: 29011,
},
{
x: 662688000000.0,
y: 27727,
},
{
x: 694224000000.0,
y: 26156,
},
{
x: 725846400000.0,
y: 24918,
},
{
x: 757382400000.0,
y: 24119,
},
{
x: 788918400000.0,
y: 23174,
},
{
x: 820454400000.0,
y: 22104,
},
{
x: 852076800000.0,
y: 21330,
},
{
x: 883612800000.0,
y: 20556,
},
{
x: 915148800000.0,
y: 20280,
},
{
x: 946684800000.0,
y: 20032,
},
{
x: 978307200000.0,
y: 18839,
},
{
x: 1009843200000.0,
y: 17400,
},
{
x: 1041379200000.0,
y: 17170,
},
{
x: 1072915200000.0,
y: 16381,
},
{
x: 1104537600000.0,
y: 15692,
},
{
x: 1136073600000.0,
y: 15083,
},
{
x: 1167609600000.0,
y: 14348,
},
{
x: 1199145600000.0,
y: 13110,
},
],
},
{
key: ['Michael'],
values: [
{
x: -157766400000.0,
y: 80812,
},
{
x: -126230400000.0,
y: 79709,
},
{
x: -94694400000.0,
y: 82204,
},
{
x: -63158400000.0,
y: 81785,
},
{
x: -31536000000.0,
y: 84893,
},
{
x: 0.0,
y: 85015,
},
{
x: 31536000000.0,
y: 77321,
},
{
x: 63072000000.0,
y: 71197,
},
{
x: 94694400000.0,
y: 67598,
},
{
x: 126230400000.0,
y: 67304,
},
{
x: 157766400000.0,
y: 68149,
},
{
x: 189302400000.0,
y: 66686,
},
{
x: 220924800000.0,
y: 67344,
},
{
x: 252460800000.0,
y: 66875,
},
{
x: 283996800000.0,
y: 67473,
},
{
x: 315532800000.0,
y: 68375,
},
{
x: 347155200000.0,
y: 68467,
},
{
x: 378691200000.0,
y: 67904,
},
{
x: 410227200000.0,
y: 67708,
},
{
x: 441763200000.0,
y: 67457,
},
{
x: 473385600000.0,
y: 64667,
},
{
x: 504921600000.0,
y: 63959,
},
{
x: 536457600000.0,
y: 63442,
},
{
x: 567993600000.0,
y: 63924,
},
{
x: 599616000000.0,
y: 65233,
},
{
x: 631152000000.0,
y: 65138,
},
{
x: 662688000000.0,
y: 60646,
},
{
x: 694224000000.0,
y: 54216,
},
{
x: 725846400000.0,
y: 49443,
},
{
x: 757382400000.0,
y: 44361,
},
{
x: 788918400000.0,
y: 41311,
},
{
x: 820454400000.0,
y: 38284,
},
{
x: 852076800000.0,
y: 37459,
},
{
x: 883612800000.0,
y: 36525,
},
{
x: 915148800000.0,
y: 33820,
},
{
x: 946684800000.0,
y: 31956,
},
{
x: 978307200000.0,
y: 29612,
},
{
x: 1009843200000.0,
y: 28156,
},
{
x: 1041379200000.0,
y: 27031,
},
{
x: 1072915200000.0,
y: 25418,
},
{
x: 1104537600000.0,
y: 23678,
},
{
x: 1136073600000.0,
y: 22498,
},
{
x: 1167609600000.0,
y: 21805,
},
{
x: 1199145600000.0,
y: 20271,
},
],
},
];

View File

@@ -1,549 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable react/jsx-key */
import { t } from '@apache-superset/core/translation';
import {
ControlPanelSectionConfig,
ControlSubSectionHeader,
CustomControlItem,
D3_TIME_FORMAT_OPTIONS,
D3_FORMAT_DOCS,
D3_FORMAT_OPTIONS,
DEFAULT_TIME_FORMAT,
} from '@superset-ui/chart-controls';
/*
Plugins in question:
AreaChartPlugin,
BarChartPlugin,
BubbleChartPlugin,
BulletChartPlugin,
CompareChartPlugin,
DistBarChartPlugin,
DualLineChartPlugin,
LineChartPlugin,
LineMultiChartPlugin,
PieChartPlugin,
TimePivotChartPlugin,
*/
export const yAxis2Format: CustomControlItem = {
name: 'y_axis_2_format',
config: {
type: 'SelectControl',
freeForm: true,
label: t('Right Axis Format'),
default: 'SMART_NUMBER',
choices: D3_FORMAT_OPTIONS,
description: D3_FORMAT_DOCS,
},
};
export const showMarkers: CustomControlItem = {
name: 'show_markers',
config: {
type: 'CheckboxControl',
label: t('Show Markers'),
renderTrigger: true,
default: false,
description: t('Show data points as circle markers on the lines'),
},
};
export const leftMargin: CustomControlItem = {
name: 'left_margin',
config: {
type: 'SelectControl',
freeForm: true,
clearable: false,
label: t('Left Margin'),
choices: [
['auto', t('auto')],
[50, '50'],
[75, '75'],
[100, '100'],
[125, '125'],
[150, '150'],
[200, '200'],
],
default: 'auto',
renderTrigger: true,
description: t(
'Left margin, in pixels, allowing for more room for axis labels',
),
},
};
export const yAxisShowMinmax: CustomControlItem = {
name: 'y_axis_showminmax',
config: {
type: 'CheckboxControl',
label: t('Y bounds'),
renderTrigger: true,
default: false,
description: t('Whether to display the min and max values of the Y-axis'),
},
};
export const yAxis2ShowMinmax: CustomControlItem = {
name: 'y_axis_2_showminmax',
config: {
type: 'CheckboxControl',
label: t('Y 2 bounds'),
renderTrigger: true,
default: false,
description: t('Whether to display the min and max values of the Y-axis'),
},
};
export const lineInterpolation: CustomControlItem = {
name: 'line_interpolation',
config: {
type: 'SelectControl',
label: t('Line Style'),
renderTrigger: true,
choices: [
['linear', t('linear')],
['basis', t('basis')],
['cardinal', t('cardinal')],
['monotone', t('monotone')],
['step-before', t('step-before')],
['step-after', t('step-after')],
],
default: 'linear',
description: t('Line interpolation as defined by d3.js'),
},
};
export const showBrush: CustomControlItem = {
name: 'show_brush',
config: {
type: 'SelectControl',
label: t('Show Range Filter'),
renderTrigger: true,
clearable: false,
default: 'auto',
choices: [
['yes', t('Yes')],
['no', t('No')],
['auto', t('Auto')],
],
description: t('Whether to display the time range interactive selector'),
},
};
export const showLegend: CustomControlItem = {
name: 'show_legend',
config: {
type: 'CheckboxControl',
label: t('Legend'),
renderTrigger: true,
default: true,
description: t('Whether to display the legend (toggles)'),
},
};
export const showControls: CustomControlItem = {
name: 'show_controls',
config: {
type: 'CheckboxControl',
label: t('Extra Controls'),
renderTrigger: true,
default: false,
description: t(
'Whether to show extra controls or not. Extra controls ' +
'include things like making multiBar charts stacked ' +
'or side by side.',
),
},
};
export const xAxisLabel: CustomControlItem = {
name: 'x_axis_label',
config: {
type: 'TextControl',
label: t('X Axis Label'),
renderTrigger: true,
default: '',
},
};
export const bottomMargin: CustomControlItem = {
name: 'bottom_margin',
config: {
type: 'SelectControl',
clearable: false,
freeForm: true,
label: t('Bottom Margin'),
choices: [
['auto', t('auto')],
[50, '50'],
[75, '75'],
[100, '100'],
[125, '125'],
[150, '150'],
[200, '200'],
],
default: 'auto',
renderTrigger: true,
description: t(
'Bottom margin, in pixels, allowing for more room for axis labels',
),
},
};
export const xTicksLayout: CustomControlItem = {
name: 'x_ticks_layout',
config: {
type: 'SelectControl',
label: t('X Tick Layout'),
choices: [
['auto', t('auto')],
['flat', t('flat')],
['45°', '45°'],
['staggered', t('staggered')],
],
default: 'auto',
clearable: false,
renderTrigger: true,
description: t('The way the ticks are laid out on the X-axis'),
},
};
export const xAxisFormat: CustomControlItem = {
name: 'x_axis_format',
config: {
type: 'SelectControl',
freeForm: true,
label: t('X Axis Format'),
renderTrigger: true,
choices: D3_TIME_FORMAT_OPTIONS,
default: DEFAULT_TIME_FORMAT,
description: D3_FORMAT_DOCS,
},
};
export const yLogScale: CustomControlItem = {
name: 'y_log_scale',
config: {
type: 'CheckboxControl',
label: t('Y Log Scale'),
default: false,
renderTrigger: true,
description: t('Use a log scale for the Y-axis'),
},
};
export const yAxisBounds: CustomControlItem = {
name: 'y_axis_bounds',
config: {
type: 'BoundsControl',
label: t('Y Axis Bounds'),
renderTrigger: true,
default: [null, null],
description: t(
'Bounds for the Y-axis. When left empty, the bounds are ' +
'dynamically defined based on the min/max of the data. Note that ' +
"this feature will only expand the axis range. It won't " +
"narrow the data's extent.",
),
},
};
export const yAxis2Bounds: CustomControlItem = {
name: 'y_axis_2_bounds',
config: {
type: 'BoundsControl',
label: t('Y Axis 2 Bounds'),
renderTrigger: true,
default: [null, null],
description: t(
'Bounds for the Y-axis. When left empty, the bounds are ' +
'dynamically defined based on the min/max of the data. Note that ' +
"this feature will only expand the axis range. It won't " +
"narrow the data's extent.",
),
},
};
export const xAxisShowMinmax: CustomControlItem = {
name: 'x_axis_showminmax',
config: {
type: 'CheckboxControl',
label: t('X bounds'),
renderTrigger: true,
default: false,
description: t('Whether to display the min and max values of the X-axis'),
},
};
export const richTooltip: CustomControlItem = {
name: 'rich_tooltip',
config: {
type: 'CheckboxControl',
label: t('Rich Tooltip'),
renderTrigger: true,
default: true,
description: t(
'The rich tooltip shows a list of all series for that point in time',
),
},
};
export const showBarValue: CustomControlItem = {
name: 'show_bar_value',
config: {
type: 'CheckboxControl',
label: t('Bar Values'),
default: false,
renderTrigger: true,
description: t('Show the value on top of the bar'),
},
};
export const barStacked: CustomControlItem = {
name: 'bar_stacked',
config: {
type: 'CheckboxControl',
label: t('Stacked Bars'),
renderTrigger: true,
default: false,
description: null,
},
};
export const reduceXTicks: CustomControlItem = {
name: 'reduce_x_ticks',
config: {
type: 'CheckboxControl',
label: t('Reduce X ticks'),
renderTrigger: true,
default: false,
description: t(
'Reduces the number of X-axis ticks to be rendered. ' +
'If true, the x-axis will not overflow and labels may be ' +
'missing. If false, a minimum width will be applied ' +
'to columns and the width may overflow into an ' +
'horizontal scroll.',
),
},
};
export const yAxisLabel: CustomControlItem = {
name: 'y_axis_label',
config: {
type: 'TextControl',
label: t('Y Axis Label'),
renderTrigger: true,
default: '',
},
};
export const timeSeriesSection: ControlPanelSectionConfig[] = [
{
label: t('Query'),
expanded: true,
controlSetRows: [
['metrics'],
['adhoc_filters'],
['groupby'],
['limit'],
['timeseries_limit_metric'],
['order_desc'],
[
{
name: 'contribution',
config: {
type: 'CheckboxControl',
label: t('Contribution'),
default: false,
description: t('Compute the contribution to the total'),
},
},
],
['row_limit', null],
],
},
{
label: t('Advanced Analytics'),
tabOverride: 'data',
description: t(
'This section contains options ' +
'that allow for advanced analytical post processing ' +
'of query results',
),
controlSetRows: [
[
<ControlSubSectionHeader>
{t('Rolling Window')}
</ControlSubSectionHeader>,
],
[
{
name: 'rolling_type',
config: {
type: 'SelectControl',
label: t('Rolling Function'),
default: 'None',
choices: [
['None', t('None')],
['mean', t('mean')],
['sum', t('sum')],
['std', t('std')],
['cumsum', t('cumsum')],
],
description: t(
'Defines a rolling window function to apply, works along ' +
'with the [Periods] text box',
),
},
},
],
[
{
name: 'rolling_periods',
config: {
type: 'TextControl',
label: t('Periods'),
isInt: true,
description: t(
'Defines the size of the rolling window function, ' +
'relative to the time granularity selected',
),
},
},
],
[
{
name: 'min_periods',
config: {
type: 'TextControl',
label: t('Min Periods'),
isInt: true,
description: t(
'The minimum number of rolling periods required to show ' +
'a value. For instance if you do a cumulative sum on 7 days ' +
'you may want your "Min Period" to be 7, so that all data points ' +
'shown are the total of 7 periods. This will hide the "ramp up" ' +
'taking place over the first 7 periods',
),
},
},
],
[
<ControlSubSectionHeader>
{t('Time Comparison')}
</ControlSubSectionHeader>,
],
[
{
name: 'time_compare',
config: {
type: 'SelectControl',
multi: true,
freeForm: true,
label: t('Time Shift'),
choices: [
['1 day', t('1 day')],
['1 week', t('1 week')],
['28 days', t('28 days')],
['30 days', t('30 days')],
['52 weeks', t('52 weeks')],
['1 year', t('1 year')],
['104 weeks', t('104 weeks')],
['2 years', t('2 years')],
['156 weeks', t('156 weeks')],
['3 years', t('3 years')],
],
description: t(
'Overlay one or more timeseries from a ' +
'relative time period. Expects relative time deltas ' +
'in natural language (example: 24 hours, 7 days, ' +
'52 weeks, 365 days). Free text is supported.',
),
},
},
],
[
{
name: 'comparison_type',
config: {
type: 'SelectControl',
label: t('Calculation type'),
default: 'values',
choices: [
['values', t('Actual Values')],
['absolute', t('Difference')],
['percentage', t('Percentage change')],
['ratio', t('Ratio')],
],
description: t(
'How to display time shifts: as individual lines; as the ' +
'difference between the main time series and each time shift; ' +
'as the percentage change; or as the ratio between series and time shifts.',
),
},
},
],
[<ControlSubSectionHeader>{t('Resample')}</ControlSubSectionHeader>],
[
{
name: 'resample_rule',
config: {
type: 'SelectControl',
freeForm: true,
label: t('Rule'),
default: null,
choices: [
['1T', t('1T')],
['1H', t('1H')],
['1D', t('1D')],
['7D', t('7D')],
['1M', t('1M')],
['1AS', t('1AS')],
],
description: t('Pandas resample rule'),
},
},
],
[
{
name: 'resample_method',
config: {
type: 'SelectControl',
freeForm: true,
label: t('Method'),
default: null,
choices: [
['asfreq', t('asfreq')],
['bfill', t('bfill')],
['ffill', t('ffill')],
['median', t('median')],
['mean', t('mean')],
['sum', t('sum')],
],
description: t('Pandas resample method'),
},
},
],
],
},
];

View File

@@ -1,82 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable react/sort-prop-types */
import PropTypes from 'prop-types';
import { ANNOTATION_TYPES } from './vendor/superset/AnnotationTypes';
export const numberOrAutoType = PropTypes.oneOfType([
PropTypes.number,
PropTypes.oneOf(['auto']),
]);
export const stringOrObjectWithLabelType = PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
label: PropTypes.string,
}),
]);
export const rgbObjectType = PropTypes.shape({
r: PropTypes.number.isRequired,
g: PropTypes.number.isRequired,
b: PropTypes.number.isRequired,
});
export const numericXYType = PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number,
});
export const categoryAndValueXYType = PropTypes.shape({
x: PropTypes.string,
y: PropTypes.number,
});
export const boxPlotValueType = PropTypes.shape({
outliers: PropTypes.arrayOf(PropTypes.number),
Q1: PropTypes.number,
Q2: PropTypes.number,
Q3: PropTypes.number,
whisker_high: PropTypes.number,
whisker_low: PropTypes.number,
});
export const bulletDataType = PropTypes.shape({
markerLabels: PropTypes.arrayOf(PropTypes.string),
markerLineLabels: PropTypes.arrayOf(PropTypes.string),
markerLines: PropTypes.arrayOf(PropTypes.number),
markers: PropTypes.arrayOf(PropTypes.number),
measures: PropTypes.arrayOf(PropTypes.number),
rangeLabels: PropTypes.arrayOf(PropTypes.string),
ranges: PropTypes.arrayOf(PropTypes.number),
});
export const annotationLayerType = PropTypes.shape({
annotationType: PropTypes.oneOf(Object.keys(ANNOTATION_TYPES)),
color: PropTypes.string,
hideLine: PropTypes.bool,
name: PropTypes.string,
opacity: PropTypes.string,
show: PropTypes.bool,
showMarkers: PropTypes.bool,
sourceType: PropTypes.string,
style: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
width: PropTypes.number,
});

View File

@@ -1,189 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @ts-nocheck -- legacy reactified component with untyped `this` context from reactify callbacks
import { reactify } from '@superset-ui/core';
import { styled } from '@apache-superset/core/theme';
import PropTypes from 'prop-types';
import Component from './NVD3Vis';
import { hideTooltips, removeTooltip } from './utils';
function componentWillUnmount() {
const { id } = this.props;
if (id !== null && id !== undefined) {
removeTooltip(id);
} else {
hideTooltips(true);
}
}
const ReactNVD3 = reactify(Component, { componentWillUnmount });
const NVD3 = ({ className, ...otherProps }) => (
<div className={className}>
<ReactNVD3 {...otherProps} />
</div>
);
NVD3.propTypes = {
className: PropTypes.string.isRequired,
};
export default styled(NVD3)`
.superset-legacy-chart-nvd3-dist-bar,
.superset-legacy-chart-nvd3-bar {
overflow-x: auto !important;
svg {
&.nvd3-svg {
width: auto;
font-size: ${({ theme }) => theme.fontSize};
}
}
}
.superset-legacy-chart-nvd3 {
nv-x text {
font-size: ${({ theme }) => theme.fontSize};
}
g.superset path {
stroke-dasharray: 5, 5;
}
.nvtooltip {
table td {
font-size: @font-size-s !important;
}
}
.nvtooltip tr.highlight td {
font-weight: ${({ theme }) => theme.fontWeightStrong};
font-size: ${({ theme }) => theme.fontSize}px !important;
}
text.nv-axislabel {
font-size: ${({ theme }) => theme.fontSize} !important;
}
g.nv-axis text {
fill: ${({ theme }) => theme.colorText};
}
g.nv-series text {
fill: ${({ theme }) => theme.colorText};
}
g.solid path,
line.solid {
stroke-dasharray: unset;
}
g.dashed path,
line.dashed {
stroke-dasharray: 5, 5;
}
g.longDashed path,
line.dotted {
stroke-dasharray: 1, 1;
}
g.opacityLow path,
line.opacityLow {
stroke-opacity: 0.2;
}
g.opacityMedium path,
line.opacityMedium {
stroke-opacity: 0.5;
}
g.opacityHigh path,
line.opacityHigh {
stroke-opacity: 0.8;
}
g.time-shift-0 path,
line.time-shift-0 {
stroke-dasharray: 5, 5;
}
g.time-shift-1 path,
line.time-shift-1 {
stroke-dasharray: 1, 5;
}
g.time-shift-2 path,
line.time-shift-3 {
stroke-dasharray: 5, 1;
}
g.time-shift-3 path,
line.time-shift-3 {
stroke-dasharray: 5, 1;
}
g.time-shift-4 path,
line.time-shift-4 {
stroke-dasharray: 5, 10;
}
g.time-shift-5 path,
line.time-shift-5 {
stroke-dasharray: 0.9;
}
g.time-shift-6 path,
line.time-shift-6 {
stroke-dasharray: 15, 10, 5;
}
g.time-shift-7 path,
line.time-shift-7 {
stroke-dasharray: 15, 10, 5, 10;
}
g.time-shift-8 path,
line.time-shift-8 {
stroke-dasharray: 15, 10, 5, 10, 15;
}
g.time-shift-9 path,
line.time-shift-9 {
stroke-dasharray: 5, 5, 1, 5;
}
.nv-noData.body {
font-size: ${({ theme }) => theme.fontSize};
font-weight: ${({ theme }) => theme.fontWeightNormal};
}
}
.superset-legacy-chart-nvd3-tr-highlight {
border-top: 1px solid;
border-bottom: 1px solid;
font-weight: ${({ theme }) => theme.fontWeightStrong};
}
.superset-legacy-chart-nvd3-tr-total {
font-weight: ${({ theme }) => theme.fontWeightStrong};
}
.nvtooltip {
.tooltip-header {
white-space: nowrap;
font-weight: ${({ theme }) => theme.fontWeightStrong};
}
tbody tr:not(.tooltip-header) td:nth-of-type(2) {
word-break: break-word;
}
}
.d3-tip.nv-event-annotation-layer-table,
.d3-tip.nv-event-annotation-layer-NATIVE {
width: 200px;
border-radius: 2px;
background-color: ${({ theme }) => theme.colorBgContainer};
fill-opacity: 0.6;
margin: ${({ theme }) => theme.sizeUnit * 2}px;
padding: ${({ theme }) => theme.sizeUnit * 2}px;
color: ${({ theme }) => theme.colorTextLightSolid};
&:after {
content: '\\25BC';
font-size: ${({ theme }) => theme.fontSize};
color: ${({ theme }) => theme.colorText};
position: absolute;
bottom: -14px;
left: 94px;
}
}
`;

View File

@@ -1,134 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@apache-superset/core/translation';
import { QueryFormData } from '@superset-ui/core';
import {
ControlPanelConfig,
D3_FORMAT_OPTIONS,
getStandardizedControls,
sections,
} from '@superset-ui/chart-controls';
import {
lineInterpolation,
showLegend,
xAxisLabel,
bottomMargin,
xAxisFormat,
yLogScale,
yAxisBounds,
xAxisShowMinmax,
yAxisShowMinmax,
yAxisLabel,
leftMargin,
} from '../NVD3Controls';
const config: ControlPanelConfig = {
controlPanelSections: [
sections.legacyTimeseriesTime,
{
label: t('Query'),
expanded: true,
controlSetRows: [
['metric'],
['adhoc_filters'],
[
{
name: 'freq',
config: {
type: 'SelectControl',
label: t('Frequency'),
default: 'W-MON',
freeForm: true,
clearable: false,
choices: [
['AS', t('Year (freq=AS)')],
['52W-MON', t('52 weeks starting Monday (freq=52W-MON)')],
['W-SUN', t('1 week starting Sunday (freq=W-SUN)')],
['W-MON', t('1 week starting Monday (freq=W-MON)')],
['D', t('Day (freq=D)')],
['4W-MON', t('4 weeks (freq=4W-MON)')],
],
description: t(
`The periodicity over which to pivot time. Users can provide
"Pandas" offset alias.
Click on the info bubble for more details on accepted "freq" expressions.`,
),
tooltipOnClick: () => {
window.open(
'https://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases',
);
},
},
},
],
],
},
{
label: t('Chart Options'),
expanded: true,
controlSetRows: [
[showLegend],
[lineInterpolation],
['color_picker', null],
],
},
{
label: t('X Axis'),
expanded: true,
controlSetRows: [
[xAxisLabel],
[bottomMargin],
[xAxisShowMinmax],
[
{
name: xAxisFormat.name,
config: {
...xAxisFormat.config,
default: 'SMART_NUMBER',
choices: D3_FORMAT_OPTIONS,
},
},
],
],
},
{
label: t('Y Axis'),
expanded: true,
controlSetRows: [
[yAxisLabel],
[leftMargin],
[yAxisShowMinmax],
[yLogScale],
['y_axis_format'],
[yAxisBounds],
],
},
],
controlOverrides: {
metric: {
clearable: false,
},
},
formDataOverrides: (formData: QueryFormData) => ({
...formData,
metric: getStandardizedControls().shiftMetric(),
}),
};
export default config;

View File

@@ -1,51 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@apache-superset/core/translation';
import { ChartMetadata, ChartPlugin } from '@superset-ui/core';
import transformProps from '../transformProps';
import thumbnail from './images/thumbnail.png';
import thumbnailDark from './images/thumbnail-dark.png';
import example from './images/example.jpg';
import exampleDark from './images/example-dark.jpg';
import controlPanel from './controlPanel';
const metadata = new ChartMetadata({
category: t('Evolution'),
credits: ['http://nvd3.org'],
description: t(
'Compares metrics between different time periods. Displays time series data across multiple periods (like weeks or months) to show period-over-period trends and patterns.',
),
exampleGallery: [{ url: example, urlDark: exampleDark }],
name: t('Time-series Period Pivot'),
tags: [t('Legacy'), t('Time'), t('nvd3')],
thumbnail,
thumbnailDark,
useLegacyApi: true,
});
export default class TimePivotChartPlugin extends ChartPlugin {
constructor() {
super({
loadChart: () => import('../ReactNVD3'),
metadata,
transformProps,
controlPanel,
});
}
}

View File

@@ -1,37 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Preset, VizType } from '@superset-ui/core';
import BubbleChartPlugin from './Bubble';
import BulletChartPlugin from './Bullet';
import CompareChartPlugin from './Compare';
import TimePivotChartPlugin from './TimePivot';
export default class NVD3ChartPreset extends Preset {
constructor() {
super({
name: 'NVD3 charts',
plugins: [
new BubbleChartPlugin().configure({ key: VizType.LegacyBubble }),
new BulletChartPlugin().configure({ key: VizType.Bullet }),
new CompareChartPlugin().configure({ key: VizType.Compare }),
new TimePivotChartPlugin().configure({ key: VizType.TimePivot }),
],
});
}
}

View File

@@ -1,206 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @ts-nocheck -- legacy transformProps with loosely-typed formData from ChartProps
import { ChartProps, VizType } from '@superset-ui/core';
import isTruthy from './utils/isTruthy';
import {
tokenizeToNumericArray,
tokenizeToStringArray,
} from './utils/tokenize';
import { formatLabel } from './utils';
const NOOP = () => {};
interface DatasourceMetric {
d3format?: string;
metric_name?: string;
}
interface NVD3Datasource {
metrics?: DatasourceMetric[];
verboseMap?: Record<string, string>;
}
const grabD3Format = (
datasource: NVD3Datasource | undefined,
targetMetric: string,
): string | undefined => {
let foundFormatter: string | undefined;
const { metrics = [] } = datasource || {};
metrics.forEach(metric => {
if (metric.d3format && metric.metric_name === targetMetric) {
foundFormatter = metric.d3format;
}
});
return foundFormatter;
};
export default function transformProps(chartProps: ChartProps) {
const {
width,
height,
annotationData,
datasource,
formData,
hooks,
queriesData,
} = chartProps;
const { onAddFilter = NOOP, onError = NOOP } = hooks;
const {
annotationLayers,
barStacked,
bottomMargin,
colorPicker,
colorScheme,
comparisonType,
contribution,
donut,
entity,
labelsOutside,
leftMargin,
lineInterpolation,
maxBubbleSize,
metric,
orderBars,
pieLabelType,
reduceXTicks,
richTooltip,
sendTimeRange,
showBarValue,
showBrush,
showControls,
showLabels,
showLegend,
showMarkers,
size,
stackedStyle,
vizType,
x,
xAxisFormat,
xAxisLabel,
xAxisShowminmax,
xLogScale,
xTicksLayout,
y,
yAxisBounds,
yAxis2Bounds,
yAxisLabel,
yAxisFormat,
yAxisShowminmax,
yAxis2Showminmax,
yLogScale,
sliceId,
} = formData;
let {
markerLabels,
markerLines,
markerLineLabels,
markers,
numberFormat,
rangeLabels,
ranges,
} = formData;
const rawData = queriesData[0].data || [];
const data = Array.isArray(rawData)
? rawData.map(row => ({
...row,
values: Array.isArray(row.values)
? row.values.map(value => ({ ...value }))
: row.values,
key: formatLabel(row.key, datasource.verboseMap),
}))
: rawData;
if (vizType === VizType.Pie) {
numberFormat = numberFormat || grabD3Format(datasource, metric);
} else if (vizType === VizType.Bullet) {
ranges = tokenizeToNumericArray(ranges) || [0, data.measures * 1.1];
rangeLabels = tokenizeToStringArray(rangeLabels);
markerLabels = tokenizeToStringArray(markerLabels);
markerLines = tokenizeToNumericArray(markerLines);
markerLineLabels = tokenizeToStringArray(markerLineLabels);
markers = tokenizeToNumericArray(markers);
}
return {
width,
height,
data,
annotationData,
annotationLayers,
areaStackedStyle: stackedStyle,
baseColor: colorPicker,
bottomMargin,
colorScheme,
comparisonType,
contribution,
entity,
isBarStacked: barStacked,
isDonut: donut,
isPieLabelOutside: labelsOutside,
leftMargin,
lineInterpolation,
markerLabels,
markerLines,
markerLineLabels,
markers,
maxBubbleSize: parseInt(maxBubbleSize, 10),
numberFormat,
onBrushEnd: isTruthy(sendTimeRange)
? timeRange => {
onAddFilter('__time_range', timeRange, false, true);
}
: undefined,
onError,
orderBars,
pieLabelType,
rangeLabels,
ranges,
reduceXTicks,
showBarValue,
showBrush,
showControls,
showLabels,
showLegend,
showMarkers,
sizeField: size,
useRichTooltip: richTooltip,
vizType,
xAxisFormat,
xAxisLabel,
xAxisShowMinMax: xAxisShowminmax,
xField: x,
xIsLogScale: xLogScale,
xTicksLayout,
yAxisFormat,
yAxisBounds,
yAxis2Bounds,
yAxisLabel,
yAxisShowMinMax: yAxisShowminmax,
yAxis2ShowMinMax: yAxis2Showminmax,
yField: y,
yIsLogScale: yLogScale,
sliceId,
};
}

View File

@@ -1,354 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @ts-nocheck -- legacy file heavily dependent on untyped d3 v3 and nvd3 APIs
import d3 from 'd3';
import d3tip from 'd3-tip';
import dompurify from 'dompurify';
import {
SMART_DATE_ID,
getTimeFormatter,
getNumberFormatter,
} from '@superset-ui/core';
// Regexp for the label added to time shifted series
// (1 hour offset, 2 days offset, etc.)
const TIME_SHIFT_PATTERN = /\d+ \w+ offset/;
const ANIMATION_TIME = 1000;
export function cleanColorInput(value) {
// for superset series that should have the same color
return String(value)
.trim()
.replace(' (right axis)', '')
.split(', ')
.filter(k => !TIME_SHIFT_PATTERN.test(k))
.join(', ');
}
/**
* If format is smart_date, format date
* Otherwise, format number with the given format name
* @param {*} format
*/
export function getTimeOrNumberFormatter(format) {
return format === SMART_DATE_ID
? getTimeFormatter(SMART_DATE_ID)
: getNumberFormatter(format);
}
export function drawBarValues(svg, data, stacked, axisFormat) {
const format = getNumberFormatter(axisFormat);
const countSeriesDisplayed = data.filter(d => !d.disabled).length;
const totalStackedValues =
stacked && data.length !== 0
? data[0].values.map((bar, iBar) => {
const bars = data
.filter(series => !series.disabled)
.map(series => series.values[iBar]);
return d3.sum(bars, d => d.y);
})
: [];
svg.selectAll('.bar-chart-label-group').remove();
setTimeout(() => {
svg.selectAll('.bar-chart-label-group').remove();
const groupLabels = svg
.select('g.nv-barsWrap')
.append('g')
.attr('class', 'bar-chart-label-group');
svg
.selectAll('g.nv-group')
.filter((d, i) => !stacked || i === countSeriesDisplayed - 1)
.selectAll('rect')
.each(function each(d, index) {
const rectObj = d3.select(this);
const transformAttr = rectObj.attr('transform');
const xPos = parseFloat(rectObj.attr('x'));
const yPos = parseFloat(rectObj.attr('y'));
const rectWidth = parseFloat(rectObj.attr('width'));
const rectHeight = parseFloat(rectObj.attr('height'));
const textEls = groupLabels
.append('text')
.text(format(stacked ? totalStackedValues[index] : d.y))
.attr('transform', transformAttr)
.attr('class', 'bar-chart-label');
// fine tune text position
const bbox = textEls.node().getBBox();
const labelWidth = bbox.width;
const labelHeight = bbox.height;
textEls.attr('x', xPos + rectWidth / 2 - labelWidth / 2);
if (rectObj.attr('class').includes('positive')) {
textEls.attr('y', yPos - 5);
} else {
textEls.attr('y', yPos + rectHeight + labelHeight);
}
});
}, ANIMATION_TIME);
}
// Formats the series key to account for a possible NULL value
function getFormattedKey(seriesKey, shouldDompurify) {
if (seriesKey === '<NULL>') {
return `&lt;${seriesKey.slice(1, -1)}&gt;`;
}
return shouldDompurify ? dompurify.sanitize(seriesKey) : seriesKey;
}
export function generateCompareTooltipContent(d, valueFormatter) {
let tooltip = '';
tooltip +=
"<table><thead><tr><td colspan='3'>" +
`<strong class='x-value'>${d.value}</strong>` +
'</td></tr></thead><tbody>';
d.series.sort((a, b) => (a.value >= b.value ? -1 : 1));
d.series.forEach(series => {
const key = getFormattedKey(series.key, true);
tooltip +=
`<tr class="${series.highlight ? 'emph' : ''}">` +
`<td class='legend-color-guide' style="opacity: ${
series.highlight ? '1' : '0.75'
};"">` +
'<div ' +
`style="border: 2px solid ${
series.highlight ? 'black' : 'transparent'
}; background-color: ${series.color};"` +
'></div>' +
'</td>' +
`<td>${key}</td>` +
`<td>${valueFormatter(series.value)}</td>` +
'</tr>';
});
tooltip += '</tbody></table>';
return dompurify.sanitize(tooltip);
}
export function generateMultiLineTooltipContent(d, xFormatter, yFormatters) {
const tooltipTitle = xFormatter(d.value);
let tooltip = '';
tooltip +=
"<table><thead><tr><td colspan='3'>" +
`<strong class='x-value'>${tooltipTitle}</strong>` +
'</td></tr></thead><tbody>';
d.series.forEach((series, i) => {
const yFormatter = yFormatters[i];
const key = getFormattedKey(series.key, true);
tooltip +=
"<tr><td class='legend-color-guide'>" +
`<div style="background-color: ${series.color};"></div></td>` +
`<td class='key'>${key}</td>` +
`<td class='value'>${yFormatter(series.value)}</td></tr>`;
});
tooltip += '</tbody></table>';
return dompurify.sanitize(tooltip);
}
export function generateTimePivotTooltip(d, xFormatter, yFormatter) {
const tooltipTitle = xFormatter(d.value);
let tooltip = '';
tooltip +=
"<table><thead><tr><td colspan='3'>" +
`<strong class='x-value'>${tooltipTitle}</strong>` +
'</td></tr></thead><tbody>';
d.series.forEach(series => {
if (series.highlight) {
let label = '';
if (series.key === 'current') {
label = series.key;
} else {
label = `${series.key} of the selected frequency:`;
}
tooltip +=
"<tr><td class='legend-color-guide'>" +
`<div style="background-color: ${series.color};"></div></td>` +
`<td class='key'>${label}</td>` +
`<td class='value'>${yFormatter(series.value)}</td></tr>`;
}
});
tooltip += '</tbody></table>';
return dompurify.sanitize(tooltip);
}
function getLabel(stringOrObjectWithLabel) {
return stringOrObjectWithLabel.label || stringOrObjectWithLabel;
}
function createHTMLRow(col1, col2) {
return `<tr><td>${col1}</td><td>${col2}</td></tr>`;
}
export function generateBubbleTooltipContent({
point,
entity,
xField,
yField,
sizeField,
xFormatter,
yFormatter,
sizeFormatter,
}) {
let s = '<table>';
s +=
`<tr><td style="color: ${point.color};">` +
`<strong>${point[entity]}</strong> (${point.group})` +
'</td></tr>';
s += createHTMLRow(getLabel(xField), xFormatter(point.x));
s += createHTMLRow(getLabel(yField), yFormatter(point.y));
s += createHTMLRow(getLabel(sizeField), sizeFormatter(point.size));
s += '</table>';
return dompurify.sanitize(s);
}
// shouldRemove indicates whether the nvtooltips should be removed from the DOM
export function hideTooltips(shouldRemove) {
const targets = document.querySelectorAll('.nvtooltip');
if (targets.length > 0) {
// Only set opacity to 0 when hiding tooltips so they would reappear
// on hover, which sets the opacity to 1
targets.forEach(t => {
if (shouldRemove) {
t.remove();
} else {
// eslint-disable-next-line no-param-reassign
t.style.opacity = 0;
}
});
}
}
export function generateTooltipClassName(uuid) {
return `tooltip-${uuid}`;
}
export function removeTooltip(uuid) {
const classSelector = `.${generateTooltipClassName(uuid)}`;
const target = document.querySelector(classSelector);
if (target) {
target.remove();
}
}
export function wrapTooltip(chart) {
const tooltipLayer =
chart.useInteractiveGuideline && chart.useInteractiveGuideline()
? chart.interactiveLayer
: chart;
const tooltipGeneratorFunc = tooltipLayer.tooltip.contentGenerator();
tooltipLayer.tooltip.contentGenerator(d => {
// The nvd3-fork default contentGenerator builds tooltip HTML with
// unescaped series keys (and feeds them into the tooltip's `.html()`
// sink at render time). Run the final string through DOMPurify so
// charts that do NOT install a custom contentGenerator (Line, Bar,
// Area, Pie, BoxPlot, etc.) cannot execute stored payloads in
// column or series names. Custom contentGenerators set elsewhere in
// this module already return sanitized output, making this a
// belt-and-braces wrap.
const tooltip = `<div>${tooltipGeneratorFunc(d)}</div>`;
return dompurify.sanitize(tooltip);
});
}
// Builds the sanitized HTML for an annotation layer's tooltip. Title and
// description values come from the annotation data source, so the output is
// run through dompurify before being inserted into the DOM by d3-tip.
export function generateAnnotationTooltipContent(layer, d) {
const title =
d[layer.titleColumn] && d[layer.titleColumn].length > 0
? `${d[layer.titleColumn]} - ${layer.name}`
: layer.name;
const body = Array.isArray(layer.descriptionColumns)
? layer.descriptionColumns.map(c => d[c])
: Object.values(d);
return dompurify.sanitize(
`<div><strong>${title}</strong></div><br/><div>${body.join(', ')}</div>`,
);
}
export function tipFactory(layer) {
return d3tip()
.attr('class', `d3-tip ${layer.annotationTipClass || ''}`)
.direction('n')
.offset([-5, 0])
.html(d => (d ? generateAnnotationTooltipContent(layer, d) : ''));
}
export function getMaxLabelSize(svg, axisClass) {
// axis class = .nv-y2 // second y axis on dual line chart
// axis class = .nv-x // x axis on time series line chart
const tickTexts = svg.selectAll(`.${axisClass} g.tick text`);
if (tickTexts.length > 0) {
const lengths = tickTexts[0].map(text => text.getComputedTextLength());
return Math.ceil(Math.max(0, ...lengths));
}
return 0;
}
export function formatLabel(input, verboseMap = {}) {
// The input for label may be a string or an array of string
// When using the time shift feature, the label contains a '---' in the array
const verboseLookup = s => verboseMap[s] || s;
return Array.isArray(input) && input.length > 0
? input
.map(l => (TIME_SHIFT_PATTERN.test(l) ? l : verboseLookup(l)))
.join(', ')
: verboseLookup(input);
}
export function stringifyTimeRange(extent) {
if (extent.some(d => d.toISOString === undefined)) {
return null;
}
return extent.map(d => d.toISOString().slice(0, -1)).join(' : ');
}
export function setAxisShowMaxMin(axis, showminmax) {
if (axis && axis.showMaxMin && showminmax !== undefined) {
axis.showMaxMin(showminmax);
}
}
export function computeYDomain(data) {
if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0].values)) {
const extents = data
.filter(d => !d.disabled)
.map(row => d3.extent(row.values, v => v.y));
const minOfMin = d3.min(extents, ([min]) => min);
const maxOfMax = d3.max(extents, ([, max]) => max);
return [minOfMin, maxOfMax];
}
return [0, 1];
}

View File

@@ -1,86 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @ts-nocheck -- vendor file; not fully typed
import { t } from '@apache-superset/core/translation';
function extractTypes(metadata) {
return Object.keys(metadata).reduce((prev, key) => {
const result = prev;
result[key] = key;
return result;
}, {});
}
export const ANNOTATION_TYPES_METADATA = {
FORMULA: {
value: 'FORMULA',
label: t('Formula'),
},
EVENT: {
value: 'EVENT',
label: t('Event'),
supportNativeSource: true,
},
INTERVAL: {
value: 'INTERVAL',
label: t('Interval'),
supportNativeSource: true,
},
TIME_SERIES: {
value: 'TIME_SERIES',
label: t('Time Series'),
},
};
export const ANNOTATION_TYPES = extractTypes(ANNOTATION_TYPES_METADATA);
export const DEFAULT_ANNOTATION_TYPE = ANNOTATION_TYPES.FORMULA;
export const ANNOTATION_SOURCE_TYPES_METADATA = {
NATIVE: {
value: 'NATIVE',
label: 'Superset annotation',
},
};
export const ANNOTATION_SOURCE_TYPES = extractTypes(
ANNOTATION_SOURCE_TYPES_METADATA,
);
export function requiresQuery(annotationSourceType) {
return !!annotationSourceType;
}
const NATIVE_COLUMN_NAMES = {
descriptionColumns: ['long_descr'],
intervalEndColumn: 'end_dttm',
timeColumn: 'start_dttm',
titleColumn: 'short_descr',
};
export function applyNativeColumns(annotation) {
if (annotation.sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
return { ...annotation, ...NATIVE_COLUMN_NAMES };
}
return annotation;
}
export default ANNOTATION_TYPES;

View File

@@ -1,57 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SqlaFormData } from '@superset-ui/core';
import * as ChartControls from '@superset-ui/chart-controls';
import controlPanel from '../../src/TimePivot/controlPanel';
const { __mockShiftMetric } = ChartControls as typeof ChartControls & {
__mockShiftMetric: jest.Mock;
};
jest.mock('@superset-ui/core', () => ({
...jest.requireActual('@superset-ui/core'),
t: (str: string) => str,
}));
jest.mock('@superset-ui/chart-controls', () => {
const original = jest.requireActual('@superset-ui/chart-controls');
const mockShiftMetric = jest.fn(() => 'shiftedMetric');
return {
...original,
getStandardizedControls: () => ({
shiftMetric: mockShiftMetric,
}),
__mockShiftMetric: mockShiftMetric,
};
});
describe('TimePivot Control Panel Config', () => {
test('should override formData metric using getStandardizedControls', () => {
const dummyFormData = { someProp: 'test' } as unknown as SqlaFormData;
const newFormData = controlPanel.formDataOverrides!(dummyFormData);
// The original properties are spread correctly.
expect(newFormData.someProp).toBe('test');
// The metric property should be replaced by the output of shiftMetric.
expect(newFormData.metric).toBe('shiftedMetric');
// Ensure that the mockShiftMetric function was called.
expect(__mockShiftMetric).toHaveBeenCalled();
});
});

View File

@@ -1,358 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
getTimeFormatterRegistry,
SMART_DATE_ID,
createSmartDateFormatter,
} from '@superset-ui/core';
import {
computeYDomain,
generateAnnotationTooltipContent,
generateBubbleTooltipContent,
generateMultiLineTooltipContent,
getTimeOrNumberFormatter,
formatLabel,
tipFactory,
} from '../src/utils';
const DATA = [
{
key: ['East Asia & Pacific'],
values: [
{
x: -315619200000.0,
y: 1031863394.0,
},
{
x: -283996800000.0,
y: 1034767718.0,
},
],
},
{
key: ['South Asia'],
values: [
{
x: -315619200000.0,
y: 572036107.0,
},
{
x: -283996800000.0,
y: 584143236.0,
},
],
},
{
key: ['Europe & Central Asia'],
values: [
{
x: -315619200000.0,
y: 660881033.0,
},
{
x: -283996800000.0,
y: 668526708.0,
},
],
},
];
const DATA_WITH_DISABLED_SERIES = [
{
disabled: true,
key: ['East Asia & Pacific'],
values: [
{
x: -315619200000.0,
y: 1031863394.0,
},
{
x: -283996800000.0,
y: 1034767718.0,
},
],
},
{
disabled: true,
key: ['South Asia'],
values: [
{
x: -315619200000.0,
y: 572036107.0,
},
{
x: -283996800000.0,
y: 584143236.0,
},
],
},
{
key: ['Europe & Central Asia'],
values: [
{
x: -315619200000.0,
y: 660881033.0,
},
{
x: -283996800000.0,
y: 668526708.0,
},
],
},
];
describe('nvd3/utils', () => {
beforeEach(() => {
getTimeFormatterRegistry().registerValue(
SMART_DATE_ID,
createSmartDateFormatter(),
);
});
describe('generateMultiLineTooltipContent()', () => {
const identity = (value: any) => value;
test('renders the series key in the tooltip markup', () => {
const tooltip = generateMultiLineTooltipContent(
{
value: 'x-value',
series: [{ key: 'Region A', color: '#fff', value: 1 }],
},
identity,
[identity],
);
expect(tooltip).toContain('Region A');
});
test('strips a script payload from a malicious series key', () => {
const tooltip = generateMultiLineTooltipContent(
{
value: 'x-value',
series: [
{
key: '<img src=x onerror="alert(1)">',
color: '#fff',
value: 1,
},
],
},
identity,
[identity],
);
// DOMPurify removes the event handler that would execute on render.
expect(tooltip).not.toContain('onerror');
expect(tooltip).not.toContain('alert(1)');
});
});
describe('getTimeOrNumberFormatter(format)', () => {
test('is a function', () => {
expect(typeof getTimeOrNumberFormatter).toBe('function');
});
test('returns a date formatter if format is smart_date', () => {
const time = new Date(Date.UTC(2018, 10, 21, 22, 11));
// @ts-expect-error -- getTimeOrNumberFormatter doesn't distinguish return types; accepts Date at runtime
expect(getTimeOrNumberFormatter('smart_date')(time)).toBe('10:11');
});
test('returns a number formatter otherwise', () => {
expect(getTimeOrNumberFormatter('.3s')(3000000)).toBe('3.00M');
expect(getTimeOrNumberFormatter(undefined)(3000100)).toBe('3M');
});
});
describe('formatLabel()', () => {
const verboseMap = {
foo: 'Foo',
bar: 'Bar',
};
test('formats simple labels', () => {
expect(formatLabel('foo')).toBe('foo');
expect(formatLabel(['foo'])).toBe('foo');
expect(formatLabel(['foo', 'bar'])).toBe('foo, bar');
});
test('formats simple labels with lookups', () => {
expect(formatLabel('foo', verboseMap)).toBe('Foo');
expect(formatLabel('baz', verboseMap)).toBe('baz');
expect(formatLabel(['foo'], verboseMap)).toBe('Foo');
expect(formatLabel(['foo', 'bar', 'baz'], verboseMap)).toBe(
'Foo, Bar, baz',
);
});
test('deals with time shift properly', () => {
expect(formatLabel(['foo', '1 hour offset'], verboseMap)).toBe(
'Foo, 1 hour offset',
);
expect(
formatLabel(['foo', 'bar', 'baz', '2 hours offset'], verboseMap),
).toBe('Foo, Bar, baz, 2 hours offset');
});
});
describe('computeYDomain()', () => {
test('works with invalid data', () => {
expect(computeYDomain('foo')).toEqual([0, 1]);
});
test('works with all series enabled', () => {
expect(computeYDomain(DATA)).toEqual([572036107.0, 1034767718.0]);
});
test('works with some series disabled', () => {
expect(computeYDomain(DATA_WITH_DISABLED_SERIES)).toEqual([
660881033.0, 668526708.0,
]);
});
});
// ------------------------------------------------------------------
// Tooltip HTML sanitisation (XSS regression).
// Each helper below feeds user-controlled column values into a
// d3 / nvd3 .html() sink; the sanitised return must strip dangerous
// markup so a stored payload cannot execute on hover.
// ------------------------------------------------------------------
describe('generateBubbleTooltipContent() sanitises user input', () => {
test('strips <script> from the entity column', () => {
const html = generateBubbleTooltipContent({
point: {
color: 'red',
group: 'g',
entity: '<script>alert(1)</script>',
x: 1,
y: 2,
size: 3,
},
entity: 'entity',
xField: 'x',
yField: 'y',
sizeField: 'size',
xFormatter: (v: number) => String(v),
yFormatter: (v: number) => String(v),
sizeFormatter: (v: number) => String(v),
});
expect(html).not.toMatch(/<script/i);
expect(html).not.toMatch(/onerror=/i);
});
test('strips <img onerror> injected via the group column', () => {
const html = generateBubbleTooltipContent({
point: {
color: 'red',
group: '<img src=x onerror=alert(1)>',
entity: 'safe',
x: 1,
y: 2,
size: 3,
},
entity: 'entity',
xField: 'x',
yField: 'y',
sizeField: 'size',
xFormatter: (v: number) => String(v),
yFormatter: (v: number) => String(v),
sizeFormatter: (v: number) => String(v),
});
expect(html).not.toMatch(/onerror/i);
});
});
describe('generateMultiLineTooltipContent() sanitises user input', () => {
test('strips <script> from a series key', () => {
const html = generateMultiLineTooltipContent(
{
value: 0,
series: [
{
key: '<script>alert(1)</script>',
color: 'red',
value: 1,
},
],
},
(v: number) => String(v),
[(v: number) => String(v)],
);
expect(html).not.toMatch(/<script/i);
});
});
describe('tipFactory() sanitises annotation columns', () => {
test('strips <script> from a description column value', () => {
const tip = tipFactory({
annotationTipClass: 'foo',
titleColumn: 'title',
descriptionColumns: ['desc'],
name: 'layer',
});
// d3-tip's .html(fn) stores the callback as the renderer; invoke
// it directly to assert the sanitised output.
const datum = {
title: 'normal',
desc: '<script>alert(1)</script>payload',
};
const html = tip.html()(datum);
expect(html).not.toMatch(/<script/i);
expect(html).toContain('payload');
});
});
describe('generateAnnotationTooltipContent()', () => {
const layer = {
name: 'My annotations',
titleColumn: 'title',
descriptionColumns: ['description'],
};
test('renders the annotation title and description', () => {
const html = generateAnnotationTooltipContent(layer, {
title: 'Release',
description: 'Shipped v1',
});
expect(html).toContain('Release - My annotations');
expect(html).toContain('Shipped v1');
});
test('falls back to the layer name when the title column is empty', () => {
const html = generateAnnotationTooltipContent(layer, {
title: '',
description: 'Shipped v1',
});
expect(html).toContain('My annotations');
});
test('strips an event-handler payload from the title column', () => {
const html = generateAnnotationTooltipContent(layer, {
title: '<img src=x onerror="alert(1)">',
description: 'ok',
});
expect(html).not.toContain('onerror');
expect(html).not.toContain('alert(1)');
});
test('strips a script payload from a description column', () => {
const html = generateAnnotationTooltipContent(layer, {
title: 'Release',
description: '<script>alert(document.cookie)</script>',
});
expect(html).not.toContain('<script>');
});
});
});

View File

@@ -1,57 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import isTruthy from '../../src/utils/isTruthy';
describe('isTruthy', () => {
test('evals false-looking strings properly', () => {
expect(isTruthy('f')).toBe(false);
expect(isTruthy('false')).toBe(false);
expect(isTruthy('no')).toBe(false);
expect(isTruthy('n')).toBe(false);
expect(isTruthy('F')).toBe(false);
expect(isTruthy('False')).toBe(false);
expect(isTruthy('NO')).toBe(false);
expect(isTruthy('N')).toBe(false);
});
test('evals true-looking strings properly', () => {
expect(isTruthy('t')).toBe(true);
expect(isTruthy('true')).toBe(true);
expect(isTruthy('yes')).toBe(true);
expect(isTruthy('y')).toBe(true);
expect(isTruthy('Y')).toBe(true);
expect(isTruthy('True')).toBe(true);
expect(isTruthy('Yes')).toBe(true);
expect(isTruthy('YES')).toBe(true);
});
test('evals bools properly', () => {
expect(isTruthy(false)).toBe(false);
expect(isTruthy(true)).toBe(true);
});
test('evals ints properly', () => {
expect(isTruthy(0)).toBe(false);
expect(isTruthy(1)).toBe(true);
});
test('evals constants properly', () => {
expect(isTruthy(null)).toBe(false);
expect(isTruthy(undefined)).toBe(false);
});
test('string auto is false', () => {
expect(isTruthy('false')).toBe(false);
});
});

View File

@@ -1,76 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
tokenizeToNumericArray,
tokenizeToStringArray,
} from '../../src/utils/tokenize';
describe('tokenizeToNumericArray', () => {
test('evals numeric strings properly', () => {
expect(tokenizeToNumericArray('1')).toStrictEqual([1]);
expect(tokenizeToNumericArray('1,2,3,4')).toStrictEqual([1, 2, 3, 4]);
expect(tokenizeToNumericArray('1.1,2.2,3.0,4')).toStrictEqual([
1.1, 2.2, 3, 4,
]);
expect(tokenizeToNumericArray(' 1, 2, 3, 4 ')).toStrictEqual([
1, 2, 3, 4,
]);
});
test('evals undefined to null', () => {
expect(tokenizeToNumericArray(undefined)).toBeNull();
});
test('evals empty strings to null', () => {
expect(tokenizeToNumericArray('')).toBeNull();
expect(tokenizeToNumericArray(' ')).toBeNull();
});
test('throws error on incorrect string', () => {
expect(() => tokenizeToNumericArray('qwerty,1,2,3')).toThrow(Error);
});
});
describe('tokenizeToStringArray', () => {
test('evals numeric strings properly', () => {
expect(tokenizeToStringArray('a')).toStrictEqual(['a']);
expect(tokenizeToStringArray('1.1 , 2.2, 3.0 ,4')).toStrictEqual([
'1.1',
'2.2',
'3.0',
'4',
]);
expect(tokenizeToStringArray('1.1,a,3, bc ,d')).toStrictEqual([
'1.1',
'a',
'3',
'bc',
'd',
]);
});
test('evals undefined to null', () => {
expect(tokenizeToStringArray(undefined)).toBeNull();
});
test('evals empty string to null', () => {
expect(tokenizeToStringArray('')).toBeNull();
expect(tokenizeToStringArray(' ')).toBeNull();
});
});

View File

@@ -1,21 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..",
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
},
"include": ["src/**/*", "types/**/*"],
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"],
"references": [
{ "path": "../../packages/superset-core" },
{ "path": "../../packages/superset-ui-core" },
{ "path": "../../packages/superset-ui-chart-controls" }
]
}

View File

@@ -17,10 +17,10 @@ specific language governing permissions and limitations
under the License.
-->
## @superset-ui/legacy-plugin-chart-calendar
## @superset-ui/plugin-chart-calendar
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-calendar.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-calendar)
[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Flegacy-plugin-chart-calendar?style=flat)](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-calendar)
[![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-calendar.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/plugin-chart-calendar)
[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Fplugin-chart-calendar?style=flat)](https://libraries.io/npm/@superset-ui%2Fplugin-chart-calendar)
This plugin provides Calendar Heatmap for Superset.
@@ -30,7 +30,7 @@ Configure `key`, which can be any `string`, and register the plugin. This `key`
lookup this chart throughout the app.
```js
import CalendarChartPlugin from '@superset-ui/legacy-plugin-chart-calendar';
import CalendarChartPlugin from '@superset-ui/plugin-chart-calendar';
new CalendarChartPlugin().configure({ key: 'calendar' }).register();
```

View File

@@ -1,18 +1,18 @@
{
"name": "@superset-ui/legacy-plugin-chart-calendar",
"name": "@superset-ui/plugin-chart-calendar",
"version": "0.20.3",
"description": "Superset Legacy Chart - Calendar Heatmap",
"description": "Superset Chart - Calendar Heatmap",
"keywords": [
"superset"
],
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-calendar#readme",
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/plugin-chart-calendar#readme",
"bugs": {
"url": "https://github.com/apache/superset/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/apache/superset.git",
"directory": "superset-frontend/plugins/legacy-plugin-chart-calendar"
"directory": "superset-frontend/plugins/plugin-chart-calendar"
},
"license": "Apache-2.0",
"author": "Superset",

View File

@@ -0,0 +1,53 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
buildQueryContext,
QueryFormData,
TimeGranularity,
} from '@superset-ui/core';
const SUBDOMAIN_TO_TIME_GRAIN: Record<string, TimeGranularity> = {
min: TimeGranularity.MINUTE,
hour: TimeGranularity.HOUR,
day: TimeGranularity.DAY,
week: TimeGranularity.WEEK,
month: TimeGranularity.MONTH,
year: TimeGranularity.YEAR,
};
/**
* Mirrors the legacy CalHeatmapViz.query_obj: a timeseries query whose
* time grain is forced from the subdomain granularity control.
*/
export default function buildQuery(formData: QueryFormData) {
const { subdomain_granularity } = formData;
const timeGrain =
SUBDOMAIN_TO_TIME_GRAIN[(subdomain_granularity as string) ?? 'min'] ??
TimeGranularity.MINUTE;
return buildQueryContext(formData, baseQueryObject => [
{
...baseQueryObject,
is_timeseries: true,
extras: {
...baseQueryObject.extras,
time_grain_sqla: timeGrain,
},
},
]);
}

View File

@@ -43,13 +43,13 @@ const metadata = new ChartMetadata({
],
thumbnail,
thumbnailDark,
useLegacyApi: true,
});
export default class CalendarChartPlugin extends ChartPlugin {
constructor() {
super({
loadChart: () => import('./ReactCalendar'),
loadBuildQuery: () => import('./buildQuery'),
metadata,
transformProps,
controlPanel,

View File

@@ -18,14 +18,14 @@
*/
import { SuperChart } from '@superset-ui/core';
import CalendarChartPlugin from '@superset-ui/legacy-plugin-chart-calendar';
import CalendarChartPlugin from '@superset-ui/plugin-chart-calendar';
import data from './data';
import { dummyDatasource, withResizableChartDemo } from '@storybook-shared';
new CalendarChartPlugin().configure({ key: 'calendar' }).register();
export default {
title: 'Legacy Chart Plugins/legacy-plugin-chart-calendar',
title: 'Chart Plugins/plugin-chart-calendar',
decorators: [withResizableChartDemo],
args: {
cellSize: 10,

View File

@@ -0,0 +1,121 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@apache-superset/core/translation';
import { DTTM_ALIAS } from '@superset-ui/core';
export interface CalHeatmapPayload {
data: Record<string, Record<string, unknown>>;
start: number;
domain?: string;
subdomain?: string;
range: number;
}
const daysInMonth = (year: number, month: number) =>
new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
/** Adds calendar months, clamping the day like dateutil.relativedelta. */
const addMonths = (date: Date, months: number): Date => {
const totalMonths = date.getUTCFullYear() * 12 + date.getUTCMonth() + months;
const year = Math.floor(totalMonths / 12);
const month = totalMonths % 12;
const day = Math.min(date.getUTCDate(), daysInMonth(year, month));
return new Date(
Date.UTC(
year,
month,
day,
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
date.getUTCMilliseconds(),
),
);
};
/**
* Calendar delta between two dates with dateutil.relativedelta semantics:
* whole months first, then remaining days (weeks = days // 7).
*/
export const calendarDelta = (start: Date, end: Date) => {
let months =
(end.getUTCFullYear() - start.getUTCFullYear()) * 12 +
(end.getUTCMonth() - start.getUTCMonth());
if (addMonths(start, months) > end) {
months -= 1;
}
const anchor = addMonths(start, months);
const days = Math.floor(
(end.getTime() - anchor.getTime()) / (24 * 60 * 60 * 1000),
);
return {
years: Math.trunc(months / 12),
months: months % 12,
weeks: Math.floor(days / 7),
};
};
/**
* Ports the legacy CalHeatmapViz.get_data reshape: per-metric value maps
* keyed by unix seconds, plus the domain range computed from the query's
* time bounds exactly like the backend's relativedelta arithmetic.
*/
export default function transformData(
records: Record<string, unknown>[],
metricLabels: string[],
fromDttm: number | null | undefined,
toDttm: number | null | undefined,
domain: string,
subdomain: string,
): CalHeatmapPayload {
if (fromDttm == null || toDttm == null) {
throw new Error(t('Please provide both time bounds (Since and Until)'));
}
const data: Record<string, Record<string, unknown>> = {};
metricLabels.forEach(metric => {
const values: Record<string, unknown> = {};
records.forEach(record => {
const timestamp = record[DTTM_ALIAS];
if (timestamp != null) {
values[String((timestamp as number) / 1000)] = record[metric];
}
});
data[metric] = values;
});
const start = new Date(fromDttm);
const end = new Date(toDttm);
const delta = calendarDelta(start, end);
const diffSecs = (toDttm - fromDttm) / 1000;
let range: number;
if (domain === 'year') {
range = end.getUTCFullYear() - start.getUTCFullYear() + 1;
} else if (domain === 'month') {
range = delta.years * 12 + delta.months + 1;
} else if (domain === 'week') {
range = delta.years * 53 + delta.weeks + 1;
} else if (domain === 'day') {
range = Math.floor(diffSecs / (24 * 60 * 60)) + 1;
} else {
range = Math.floor(diffSecs / (60 * 60)) + 1;
}
return { data, start: fromDttm, domain, subdomain, range };
}

View File

@@ -17,8 +17,15 @@
* under the License.
*/
import { ChartProps, getNumberFormatter } from '@superset-ui/core';
import {
ChartProps,
ensureIsArray,
getMetricLabel,
getNumberFormatter,
QueryFormMetric,
} from '@superset-ui/core';
import { getFormattedUTCTime } from './utils';
import transformData from './transformData';
export default function transformProps(chartProps: ChartProps) {
const { height, formData, queriesData, datasource } = chartProps;
@@ -44,9 +51,29 @@ export default function transformProps(chartProps: ChartProps) {
getFormattedUTCTime(ts, xAxisTimeFormat);
const valueFormatter = getNumberFormatter(yAxisFormat);
// The legacy explore_json endpoint computed the per-metric value maps
// and domain range server-side; v1 responses arrive as flat records.
const {
data: rawData,
from_dttm: fromDttm,
to_dttm: toDttm,
} = queriesData[0];
const data = Array.isArray(rawData)
? transformData(
rawData,
ensureIsArray(formData.metrics as QueryFormMetric[]).map(
getMetricLabel,
),
fromDttm,
toDttm,
domainGranularity,
subdomainGranularity,
)
: rawData;
return {
height,
data: queriesData[0].data,
data,
cellPadding,
cellRadius,
cellSize,

View File

@@ -0,0 +1,53 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { QueryFormData } from '@superset-ui/core';
import buildQuery from '../src/buildQuery';
const formData: QueryFormData = {
datasource: '5__table',
granularity_sqla: 'ds',
time_range: 'Last year',
viz_type: 'cal_heatmap',
metrics: ['sum__num'],
domain_granularity: 'month',
subdomain_granularity: 'day',
};
test('forces the time grain from the subdomain granularity', () => {
const [query] = buildQuery(formData).queries;
expect(query.extras?.time_grain_sqla).toEqual('P1D');
expect(query.is_timeseries).toBe(true);
expect(query.metrics).toEqual(['sum__num']);
});
test('defaults the time grain to minutes like the legacy backend', () => {
const [query] = buildQuery({
...formData,
subdomain_granularity: undefined,
}).queries;
expect(query.extras?.time_grain_sqla).toEqual('PT1M');
});
test('falls back to minutes for an unrecognized subdomain granularity', () => {
const [query] = buildQuery({
...formData,
subdomain_granularity: 'fortnight',
}).queries;
expect(query.extras?.time_grain_sqla).toEqual('PT1M');
});

View File

@@ -16,13 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import controlPanel from '../src/controlPanel';
declare module '*.png' {
const value: string;
export default value;
}
declare module '*.jpg' {
const value: string;
export default value;
}
test('exposes a datetime column control because the query is is_timeseries', () => {
expect(JSON.stringify(controlPanel)).toContain('granularity_sqla');
});

View File

@@ -1,4 +1,4 @@
/*
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
@@ -16,9 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
import CalendarChartPlugin from '../src/index';
export { default as NVD3ChartPreset } from './preset';
export { default as BubbleChartPlugin } from './Bubble';
export { default as BulletChartPlugin } from './Bullet';
export { default as CompareChartPlugin } from './Compare';
export { default as TimePivotChartPlugin } from './TimePivot';
test('registers the plugin with its v1 metadata and buildQuery loader', () => {
const plugin = new CalendarChartPlugin();
expect(plugin.metadata.name).toEqual('Calendar Heatmap');
expect(plugin.loadBuildQuery).toBeDefined();
});

View File

@@ -0,0 +1,92 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import transformData, { calendarDelta } from '../src/transformData';
const jan1 = Date.UTC(2024, 0, 1); // 1704067200000
const jan2 = Date.UTC(2024, 0, 2);
test('keys per-metric values by unix seconds like the legacy backend', () => {
const { data, start } = transformData(
[
{ __timestamp: jan1, sum__num: 10 },
{ __timestamp: jan2, sum__num: 20 },
],
['sum__num'],
jan1,
Date.UTC(2024, 11, 31),
'month',
'day',
);
expect(data.sum__num).toEqual({
'1704067200': 10,
'1704153600': 20,
});
expect(start).toEqual(jan1);
});
test('throws when time bounds are missing', () => {
expect(() =>
transformData([], ['sum__num'], null, jan1, 'month', 'day'),
).toThrow('time bounds');
});
test.each([
['year', Date.UTC(2022, 5, 15), Date.UTC(2024, 1, 1), 3],
// Jan 15 -> Apr 10 is 2 whole months + 26 days => 2 + 1
['month', Date.UTC(2024, 0, 15), Date.UTC(2024, 3, 10), 3],
['day', jan1, Date.UTC(2024, 0, 8, 12), 8],
['hour', jan1, Date.UTC(2024, 0, 1, 5, 30), 6],
])('computes the %s domain range', (domain, start, end, expected) => {
const { range } = transformData(
[],
['m'],
start as number,
end as number,
domain as string,
'day',
);
expect(range).toEqual(expected);
});
test('computes week ranges with relativedelta semantics', () => {
// 2024-01-01 -> 2024-03-20: 2 full months + 19 days => weeks = 2
const { range } = transformData(
[],
['m'],
Date.UTC(2024, 0, 1),
Date.UTC(2024, 2, 20),
'week',
'day',
);
expect(range).toEqual(0 * 53 + 2 + 1);
});
test('calendarDelta matches dateutil for month-end clamping', () => {
// Jan 31 -> Feb 29 is a full month in dateutil terms (clamped day)
expect(
calendarDelta(
new Date(Date.UTC(2024, 0, 31)),
new Date(Date.UTC(2024, 1, 29)),
),
).toEqual({
years: 0,
months: 1,
weeks: 0,
});
});

View File

@@ -0,0 +1,88 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { supersetTheme } from '@apache-superset/core/theme';
import { ChartProps } from '@superset-ui/core';
import transformProps from '../src/transformProps';
const baseChartProps = {
width: 800,
height: 600,
datasource: { verboseMap: { SUM__value: 'Sum value' } },
theme: supersetTheme,
hooks: {},
};
const formData = {
metrics: ['SUM__value'],
domainGranularity: 'month',
subdomainGranularity: 'day',
linearColorScheme: 'schemeRdYlBu',
cellSize: 10,
cellPadding: 2,
cellRadius: 0,
steps: 10,
yAxisFormat: '.2f',
xAxisTimeFormat: '%b %d',
showLegend: true,
showValues: false,
showMetricName: true,
sliceId: 1,
};
test('reshapes v1 records into calendar data and passes through display options', () => {
const chartProps = new ChartProps({
...baseChartProps,
formData,
queriesData: [
{
data: [
{ __timestamp: Date.UTC(2020, 0, 1), SUM__value: 3 },
{ __timestamp: Date.UTC(2020, 0, 2), SUM__value: 7 },
],
from_dttm: Date.UTC(2020, 0, 1),
to_dttm: Date.UTC(2020, 0, 31),
},
],
});
const result = transformProps(chartProps) as Record<string, unknown>;
// The reshape produces a per-metric value map, and the display controls and
// formatters are forwarded to the chart.
expect(result.data).toBeDefined();
expect(result.domainGranularity).toBe('month');
expect(result.subdomainGranularity).toBe('day');
expect(result.cellSize).toBe(10);
expect(result.showMetricName).toBe(true);
expect(typeof result.timeFormatter).toBe('function');
expect(typeof result.valueFormatter).toBe('function');
expect(result.verboseMap).toEqual({ SUM__value: 'Sum value' });
});
test('passes through non-array data untouched (already-shaped payload)', () => {
const preShaped = { SUM__value: { 1577836800000: 3 } };
const chartProps = new ChartProps({
...baseChartProps,
formData,
queriesData: [{ data: preShaped, from_dttm: null, to_dttm: null }],
});
const result = transformProps(chartProps) as Record<string, unknown>;
expect(result.data).toBe(preShaped);
});

View File

@@ -17,10 +17,10 @@ specific language governing permissions and limitations
under the License.
-->
## @superset-ui/legacy-plugin-chart-chord
## @superset-ui/plugin-chart-chord
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-chord.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-chord)
[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Flegacy-plugin-chart-chord?style=flat)](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-chord)
[![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-chord.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/plugin-chart-chord)
[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Fplugin-chart-chord?style=flat)](https://libraries.io/npm/@superset-ui%2Fplugin-chart-chord)
This plugin provides Chord Diagram for Superset.
@@ -30,7 +30,7 @@ Configure `key`, which can be any `string`, and register the plugin. This `key`
lookup this chart throughout the app.
```js
import ChordChartPlugin from '@superset-ui/legacy-plugin-chart-chord';
import ChordChartPlugin from '@superset-ui/plugin-chart-chord';
new ChordChartPlugin().configure({ key: 'chord' }).register();
```

View File

@@ -1,7 +1,7 @@
{
"name": "@superset-ui/legacy-plugin-chart-chord",
"name": "@superset-ui/plugin-chart-chord",
"version": "0.20.3",
"description": "Superset Legacy Chart - Chord Diagram",
"description": "Superset Chart - Chord Diagram",
"sideEffects": [
"*.css"
],
@@ -14,7 +14,7 @@
"repository": {
"type": "git",
"url": "https://github.com/apache/superset.git",
"directory": "superset-frontend/plugins/legacy-plugin-chart-chord"
"directory": "superset-frontend/plugins/plugin-chart-chord"
},
"keywords": [
"superset"
@@ -24,7 +24,7 @@
"bugs": {
"url": "https://github.com/apache/superset/issues"
},
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-chord#readme",
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/plugin-chart-chord#readme",
"publishConfig": {
"access": "public"
},

View File

@@ -0,0 +1,47 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
buildQueryContext,
ensureIsArray,
QueryFormColumn,
QueryFormData,
} from '@superset-ui/core';
/**
* Mirrors the legacy ChordViz.query_obj: one query grouped by the source
* (groupby) and target (columns) dimensions, selecting the single metric,
* ordered by that metric when sort_by_metric is on.
*/
export default function buildQuery(formData: QueryFormData) {
const { groupby, columns, metric, sort_by_metric } = formData;
const source = ensureIsArray(
groupby as QueryFormColumn | QueryFormColumn[],
)[0];
const target = ensureIsArray(
columns as QueryFormColumn | QueryFormColumn[],
)[0];
return buildQueryContext(formData, baseQueryObject => [
{
...baseQueryObject,
columns: [source, target].filter(column => column != null),
metrics: metric ? [metric] : [],
orderby: sort_by_metric && metric ? [[metric, false]] : undefined,
},
]);
}

Some files were not shown because too many files have changed in this diff Show More