Skip to content

Commit

Permalink
Merge pull request #3 from DataShades/LLCAXCHZF-56
Browse files Browse the repository at this point in the history
LLCAXCHZF-56
  • Loading branch information
TomeCirun authored Jul 29, 2024
2 parents 91ea83c + 1479552 commit 8abbfc1
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 4 deletions.
2 changes: 1 addition & 1 deletion ckanext/charts/assets/css/charts.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 111 additions & 0 deletions ckanext/charts/assets/js/vendor/chartjs-adapter-moment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
Purpose:
To handle and display date and time data in Chart.js scatter and bubble charts with type: 'time' on the x-axis.
Why We Use It:
1. Date Parsing and Formatting:
- Enables robust parsing and formatting of date strings using the moment library.
2. Chart.js Compatibility:
- Integrates with Chart.js for time-based data handling.
- Necessary for using type: 'time' in chart configurations.
3. Advanced Date Manipulation:
- Provides functions for date manipulation like adding/subtracting time and calculating date differences.
Example Usage:
To use type: 'time' for the x-axis in scatter and bubble charts:
const config = {
type: 'scatter',
data: {...},
options: {
scales: {
x: {
type: 'time',
time: {
unit: 'day'
}
}
}
}
};
The moment adapter ensures accurate processing and display of date and time data in Chart.js.
*/


/*!
* chartjs-adapter-moment v1.0.0
* https://www.chartjs.org
* (c) 2021 chartjs-adapter-moment Contributors
* Released under the MIT license
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('moment'), require('chart.js')) :
typeof define === 'function' && define.amd ? define(['moment', 'chart.js'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.moment, global.Chart));
}(this, (function (moment, chart_js) { 'use strict';

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);

const FORMATS = {
datetime: 'MMM D, YYYY, h:mm:ss a',
millisecond: 'h:mm:ss.SSS a',
second: 'h:mm:ss a',
minute: 'h:mm a',
hour: 'hA',
day: 'MMM D',
week: 'll',
month: 'MMM YYYY',
quarter: '[Q]Q - YYYY',
year: 'YYYY'
};

chart_js._adapters._date.override(typeof moment__default['default'] === 'function' ? {
_id: 'moment', // DEBUG ONLY

formats: function() {
return FORMATS;
},

parse: function(value, format) {
if (typeof value === 'string' && typeof format === 'string') {
value = moment__default['default'](value, format);
} else if (!(value instanceof moment__default['default'])) {
value = moment__default['default'](value);
}
return value.isValid() ? value.valueOf() : null;
},

format: function(time, format) {
return moment__default['default'](time).format(format);
},

add: function(time, amount, unit) {
return moment__default['default'](time).add(amount, unit).valueOf();
},

diff: function(max, min, unit) {
return moment__default['default'](max).diff(moment__default['default'](min), unit);
},

startOf: function(time, unit, weekday) {
time = moment__default['default'](time);
if (unit === 'isoWeek') {
weekday = Math.trunc(Math.min(Math.max(0, weekday), 6));
return time.isoWeekday(weekday).startOf('day').valueOf();
}
return time.startOf(unit).valueOf();
},

endOf: function(time, unit) {
return moment__default['default'](time).endOf(unit).valueOf();
}
} : {});

})));
1 change: 1 addition & 0 deletions ckanext/charts/assets/webassets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ chartjs:
output: ckanext-charts/%(version)s-chartjs.js
contents:
- js/vendor/chartjs.min.js
- js/vendor/chartjs-adapter-moment.js
- js/charts-render-chartjs.js
extra:
preload:
Expand Down
12 changes: 12 additions & 0 deletions ckanext/charts/chart_builders/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,18 @@ def height_field(self) -> dict[str, Any]:
"group": "Data",
}

def more_info_button_field(self) -> dict[str, Any]:
"""
Adds a "More info" button to the Data tab in the form, which triggers a pop-up.
This pop-up provides users with information about supported date formats.
"""
return {
"field_name": "more_info",
"label": "More info",
"form_snippet": "chart_more_info_button.html",
"group": "Data",
}

def size_field(self, choices: list[dict[str, str]]) -> dict[str, Any]:
field = self.column_field(choices)
field.update({"field_name": "size", "label": "Size", "group": "Structure"})
Expand Down
34 changes: 31 additions & 3 deletions ckanext/charts/chart_builders/chartjs.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_multi_axis_field(columns),
self.more_info_button_field(),
self.limit_field(),
self.filter_field(columns),
]
Expand Down Expand Up @@ -155,6 +156,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_multi_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.limit_field(),
Expand Down Expand Up @@ -213,6 +215,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.values_field(columns),
self.names_field(columns),
self.more_info_button_field(),
self.limit_field(),
self.filter_field(columns),
]
Expand Down Expand Up @@ -254,8 +257,32 @@ def to_json(self) -> str:
"data": dataset_data,
}
]
return json.dumps(self._configure_date_axis(data))

def _configure_date_axis(self, data: dict[str, Any]) -> dict[str, Any]:
"""
Configure date settings for the x-axis if it uses 'date_time'.
"""
x_axis = data["options"]["x"]
scales = data["options"].get("scales", {})

if x_axis == "date_time":
x_scale = scales.get("x", {})
x_scale.update(
{
"type": "time",
"time": {
"unit": "day",
"displayFormats": {"day": "YYYY-MM-DD"},
},
}
)
scales["x"] = x_scale

return json.dumps(data)
if scales:
data["options"]["scales"] = scales

return data


class ChartJSScatterForm(BaseChartForm):
Expand All @@ -277,6 +304,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.limit_field(),
Expand Down Expand Up @@ -312,8 +340,7 @@ def to_json(self) -> str:
data["data"]["datasets"] = [
{"label": self.settings["y"], "data": dataset_data},
]

return json.dumps(data)
return json.dumps(self._configure_date_axis(data))

def _calculate_bubble_radius(self, data_series: pd.Series, max_size: int) -> int:
"""Calculate bubble radius based on the size column"""
Expand Down Expand Up @@ -403,6 +430,7 @@ def get_form_fields(self):
columns,
help_text="Select 3 or more different categorical variables (dimensions)",
),
self.more_info_button_field(),
self.limit_field(),
self.filter_field(columns),
]
5 changes: 5 additions & 0 deletions ckanext/charts/chart_builders/observable.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.fill_field(columns),
Expand Down Expand Up @@ -112,6 +113,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.invert_x_field(),
self.invert_y_field(),
self.sort_x_field(),
Expand Down Expand Up @@ -187,6 +189,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.values_field(columns),
self.names_field(columns),
self.more_info_button_field(),
self.opacity_field(),
self.inner_radius_field(),
self.stroke_width_field(),
Expand Down Expand Up @@ -227,6 +230,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.color_field(columns),
Expand Down Expand Up @@ -265,6 +269,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.sort_x_field(),
self.sort_y_field(),
self.color_field(columns),
Expand Down
4 changes: 4 additions & 0 deletions ckanext/charts/chart_builders/plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.log_x_field(),
self.log_y_field(),
self.sort_x_field(),
Expand Down Expand Up @@ -143,6 +144,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.values_field(columns),
self.names_field(columns),
self.more_info_button_field(),
self.opacity_field(),
self.limit_field(),
self.filter_field(columns),
Expand Down Expand Up @@ -185,6 +187,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.plotly_y_multi_axis_field(columns, 2),
self.more_info_button_field(),
self.invert_x_field(),
self.invert_y_field(),
self.sort_x_field(),
Expand Down Expand Up @@ -229,6 +232,7 @@ def get_form_fields(self):
self.type_field(chart_types),
self.x_axis_field(columns),
self.y_axis_field(columns),
self.more_info_button_field(),
self.log_x_field(),
self.log_y_field(),
self.sort_x_field(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<div class="form-group control-full control-large">
<div class="controls">
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#moreInfoModal">
{{ _('More info') }}
</button>
</div>
</div>

{% set datetime_mapping = {
"YYYY-MM-DD": "2023-07-24",
"YYYY/MM/DD": "2023/07/24",
"MM-DD-YYYY": "07-24-2023",
"MM/DD/YYYY": "07/24/2023",
"DD-MM-YYYY": "24-07-2023",
"DD/MM/YYYY": "24/07/2023",
"dd/MMM/yyyy": "25/Nov/2023",
"YYYYMMDD": "20231125",
"YYYY-MM-DDTHH:MM:SS": "2023-07-24T14:30:00",
"YYYY-MM-DD HH:mm:ss": "2023-11-25 12:34:56",
"YYYY-MM-DDTHH:mm:ssZ": "2023-11-25T12:34:56Z",
"YYYY-MM-DD HH:mm:ss.SSS": "2023-11-25 12:34:56.789",
"MM/dd/yyyy hh:mm:ss a": "9/28/2023 2:23:15 PM",
"MM/dd/yyyy hh:mm:ss a:SSS": "8/5/2023 3:31:18 AM:234",
"MMdd_HH:mm:ss.SSS": "0423_11:42:35.883",
"dd MMM yyyy HH:mm:ss*SSS": "23 Apr 2023 10:32:35*311",
"dd MMM yyyy HH:mm:ss": "23 Apr 2023 11:42:35",
"yyMMdd HH:mm:ss": "220423 11:42:35",
"yy-MM-dd HH:mm:ss": "23-04-19 12:00:17"
} %}

<div class="modal fade" id="moreInfoModal" tabindex="-1" role="dialog" aria-labelledby="moreInfoModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="moreInfoModalLabel">{{ _("Supported Date Formats") }}</h5>
<button type="button" class="btn-close close" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<div class="date-formats-container">
<table class="table">
<thead>
<tr>
<th>{{ _("Timestamp Format") }}</th>
<th>{{ _("Example") }}</th>
</tr>
</thead>

{% for format, example in datetime_mapping.items() %}
<tr>
<td>{{ format }}</td>
<td>{{ example }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">{{ _("Close") }}</button>
</div>
</div>
</div>
</div>
5 changes: 5 additions & 0 deletions ckanext/charts/theme/mixins.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.modal-open {
.module-resource {
z-index: unset;
}
}

0 comments on commit 8abbfc1

Please sign in to comment.